diff --git a/wuttapos/controls/txnitem.py b/wuttapos/controls/txnitem.py index 1550030..4c6891a 100644 --- a/wuttapos/controls/txnitem.py +++ b/wuttapos/controls/txnitem.py @@ -42,18 +42,11 @@ class WuttaTxnItem(WuttaControl): def build(self): - kw = {} - if self.row.void: - kw['color'] = 'red' - kw['decoration'] = ft.TextDecoration.LINE_THROUGH - self.major_style = ft.TextStyle(size=self.font_size, - weight=ft.FontWeight.BOLD, - **kw) + weight=ft.FontWeight.BOLD) self.minor_style = ft.TextStyle(size=int(self.font_size * 0.8), - italic=True, - **kw) + italic=True) if self.row.row_type == self.enum.POS_ROW_TYPE_SELL: return self.build_item_sell() @@ -63,22 +56,33 @@ class WuttaTxnItem(WuttaControl): return self.build_item_tender() def build_item_sell(self): - quantity = self.app.render_quantity(self.row.quantity) - pretty_price = self.app.render_currency(self.row.txn_price) + + self.quantity = ft.TextSpan(style=self.minor_style) + self.txn_price = ft.TextSpan(style=self.minor_style) + + self.sales_total_style = ft.TextStyle(size=self.font_size, + weight=ft.FontWeight.BOLD) + + self.sales_total = ft.TextSpan(style=self.sales_total_style) + + # set initial text display values + self.refresh(update=False) + return ft.Row( [ ft.Text( spans=[ ft.TextSpan(f"{self.row.description}", style=self.major_style), - ft.TextSpan(f"× {quantity} @ {pretty_price}", - style=self.minor_style), + ft.TextSpan("× ", style=self.minor_style), + self.quantity, + ft.TextSpan(" @ ", style=self.minor_style), + self.txn_price, ], ), ft.Text( spans=[ - ft.TextSpan(self.app.render_currency(self.row.sales_total), - style=self.major_style), + self.sales_total, ], ), @@ -106,15 +110,33 @@ class WuttaTxnItem(WuttaControl): alignment=ft.MainAxisAlignment.SPACE_BETWEEN, ) - def mark_void(self): + def refresh(self, update=True): - # TODO: how to properly handle this restriction? - assert self.row.row_type == self.enum.POS_ROW_TYPE_SELL + if self.row.void: + self.major_style.color = 'red' + self.major_style.decoration = ft.TextDecoration.LINE_THROUGH + self.minor_style.color = 'red' + self.minor_style.decoration = ft.TextDecoration.LINE_THROUGH + else: + self.major_style.color = None + self.major_style.decoration = None + self.minor_style.color = None + self.minor_style.decoration = None - self.major_style.color = 'red' - self.major_style.decoration = ft.TextDecoration.LINE_THROUGH + if self.row.row_type == self.enum.POS_ROW_TYPE_SELL: + self.quantity.text = self.app.render_quantity(self.row.quantity) + self.txn_price.text = self.app.render_currency(self.row.txn_price) + self.sales_total.text = self.app.render_currency(self.row.sales_total) - self.minor_style.color = 'red' - self.minor_style.decoration = ft.TextDecoration.LINE_THROUGH + if self.row.void: + self.sales_total_style.color = 'red' + self.sales_total_style.decoration = ft.TextDecoration.LINE_THROUGH + else: + if self.row.txn_price < self.row.reg_price: + self.sales_total_style.color = 'green' + else: + self.sales_total_style.color = None + self.sales_total_style.decoration = None - self.update() + if update: + self.update() diff --git a/wuttapos/util.py b/wuttapos/util.py index ef994b3..3c93aa4 100644 --- a/wuttapos/util.py +++ b/wuttapos/util.py @@ -44,7 +44,8 @@ def make_button(text, font_size=24, font_bold=True, font_weight=None, **kwargs): """ if not font_weight and font_bold: font_weight = ft.FontWeight.BOLD - text = ft.Text(text, size=font_size, weight=font_weight) + text = ft.Text(text, size=font_size, weight=font_weight, + text_align=ft.TextAlign.CENTER) kwargs.setdefault('alignment', ft.alignment.center) kwargs.setdefault('border', ft.border.all(1, 'black')) diff --git a/wuttapos/views/base.py b/wuttapos/views/base.py index 8576dad..175f2bd 100644 --- a/wuttapos/views/base.py +++ b/wuttapos/views/base.py @@ -87,6 +87,16 @@ class WuttaView(ft.View): kwargs.setdefault('height', 100) return ft.Image(src=logo, **kwargs) + def show_snackbar(self, text, bgcolor='yellow', update=True): + self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black', + size=20, + weight=ft.FontWeight.BOLD), + bgcolor=bgcolor, + duration=1500) + self.page.snack_bar.open = True + if update: + self.page.update() + class WuttaViewContainer(ft.Container): """ diff --git a/wuttapos/views/pos.py b/wuttapos/views/pos.py index e4d444c..b8de914 100644 --- a/wuttapos/views/pos.py +++ b/wuttapos/views/pos.py @@ -140,10 +140,15 @@ class POSView(WuttaView): if row: session.commit() - self.add_row_item(row) - self.items.scroll_to(offset=-1, duration=100) - self.txn_total.value = self.app.render_currency(batch.get_balance()) - self.reset() + + if row.row_type == self.enum.POS_ROW_TYPE_BADPRICE: + self.show_snackbar(f"Product has invalid price: {row.item_entry}") + + else: + self.add_row_item(row) + self.items.scroll_to(offset=-1, duration=100) + self.txn_total.value = self.app.render_currency(batch.get_balance()) + self.reset() else: @@ -608,8 +613,7 @@ class POSView(WuttaView): self.set_quantity.data = None self.set_quantity.value = None elif self.selected_item: - self.selected_item.bgcolor = 'white' - self.selected_item = None + self.clear_item_selection() self.main_input.focus() self.page.update() @@ -625,39 +629,36 @@ class POSView(WuttaView): meta_button_width = meta_button_height * 2 meta_font_size = self.default_font_size - def meta_button(text, on_click=None, bgcolor='blue', data=None, - font_size=None): - return ft.Container(content=ft.Text(text, size=font_size or meta_font_size, - weight=ft.FontWeight.BOLD), - height=meta_button_height, - width=meta_button_width, - on_click=on_click, - alignment=ft.alignment.center, - border=ft.border.all(1, 'black'), - border_radius=ft.border_radius.all(5), - bgcolor=bgcolor, - data=data) + def meta_button(text, font_size=None, bgcolor='blue', data=None, on_click=None): + return self.make_button(text, + font_size=font_size or meta_font_size, + height=meta_button_height, + width=meta_button_width, + bgcolor=bgcolor, + data=data, + on_click=on_click) self.meta_menu = ft.Container( content=ft.Column( [ ft.Row( [ - meta_button("ITEM", bgcolor='blue', on_click=self.item_click), + meta_button("CUST", bgcolor='blue', on_click=self.customer_click), meta_button("VOID", bgcolor='red', on_click=self.void_click), ], spacing=0, ), ft.Row( [ - meta_button("CUST", bgcolor='blue', on_click=self.customer_click), + meta_button("ITEM", bgcolor='blue', on_click=self.item_click), meta_button("MGR", bgcolor='yellow', on_click=self.not_supported), ], spacing=0, ), ft.Row( [ - meta_button("TODO", bgcolor='blue', on_click=self.not_supported), + meta_button("Adjust\nPrice", font_size=30, bgcolor='yellow', + on_click=self.adjust_price_click), meta_button("TODO", bgcolor='blue', on_click=self.not_supported), ], spacing=0, @@ -849,7 +850,7 @@ class POSView(WuttaView): text = "NOT YET SUPPORTED" if not feature and e: - feature = e.control.content.value + feature = e.control.content.value.replace('\n', ' ') if feature: text += f": {feature}" self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black', @@ -860,6 +861,148 @@ class POSView(WuttaView): self.page.snack_bar.open = True self.page.update() + def adjust_price_click(self, e): + + if not len(self.items.controls): + self.show_snackbar("There are no line items", update=False) + self.reset() + return + + if not self.selected_item: + self.show_snackbar("Must first select a line item", update=False) + self.main_input.focus() + self.page.update() + return + + def cancel(e): + dlg.open = False + self.main_input.focus() + self.page.update() + + def clear(e): + price_override.value = '' + price_override.focus() + self.page.update() + + def tenkey_char(key): + price_override.value = f"{price_override.value or ''}{key}" + self.page.update() + + def confirm(e): + dlg.open = False + + try: + price = decimal.Decimal(price_override.value) + except decimal.InvalidOperation: + self.show_snackbar(f"Price is not valid: {price_override.value}", update=False) + self.main_input.focus() + self.page.update() + return + + session = self.app.make_session() + user = self.get_current_user(session) + handler = self.get_batch_handler() + + row = self.selected_item.data['row'] + row = session.get(row.__class__, row.uuid) + + new_row = handler.override_price(row, user, price) + session.commit() + + # update screen to reflect new balance + batch = row.batch + self.txn_total.value = self.app.render_currency(batch.get_balance()) + + # update item display + self.selected_item.data['row'] = row + self.selected_item.content.row = row + self.selected_item.content.refresh() + self.items.update() + + session.expunge_all() + session.close() + self.clear_item_selection() + self.reset() + + price_override = ft.TextField(value=self.main_input.value, + text_size=32, + text_style=ft.TextStyle(weight=ft.FontWeight.BOLD), + autofocus=True, + on_submit=confirm) + + dlg = ft.AlertDialog( + modal=True, + title=ft.Text("Adjust Price"), + content=ft.Container( + ft.Column( + [ + ft.Divider(), + ft.Row( + [ + ft.Text("Reg Price:", + size=32, weight=ft.FontWeight.BOLD), + ft.Text(self.app.render_currency(self.selected_item.data['row'].reg_price), + size=32, weight=ft.FontWeight.BOLD), + ], + ), + ft.Row(), + ft.Row( + [ + ft.Text("Txn Price:", + size=32, weight=ft.FontWeight.BOLD), + ft.Text(self.app.render_currency(self.selected_item.data['row'].txn_price), + size=32, weight=ft.FontWeight.BOLD), + ], + ), + ft.Row(), + ft.Row(), + ft.Row( + [ + ft.Text("New Price:", + size=32, weight=ft.FontWeight.BOLD), + ft.VerticalDivider(), + ft.Text("$", size=32, weight=ft.FontWeight.BOLD), + price_override, + ], + ), + ft.Row(), + ft.Row(), + ft.Row( + [ + WuttaTenkeyMenu(self.config, simple=True, + on_char=tenkey_char, + on_enter=confirm), + self.make_button("Clear", + height=self.default_button_size * 0.8, + width=self.default_button_size * 1.2, + on_click=clear), + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, + vertical_alignment=ft.CrossAxisAlignment.START, + ), + ], + ), + height=700, + width=550, + ), + actions=[ + self.make_button("Cancel", + height=self.default_button_size * 0.8, + width=self.default_button_size * 1.2, + on_click=cancel), + self.make_button("Confirm", + bgcolor='blue', + height=self.default_button_size * 0.8, + width=self.default_button_size * 1.2, + on_click=confirm), + ], + actions_alignment=ft.MainAxisAlignment.END, + ) + + self.page.dialog = dlg + dlg.open = True + self.page.update() + def add_row_item(self, row): # TODO: row types ugh @@ -872,7 +1015,7 @@ class POSView(WuttaView): ft.Container( content=WuttaTxnItem(self.config, row), border=ft.border.only(bottom=ft.border.BorderSide(1, 'gray')), - padding=ft.padding.only(0, 5, 0, 5), + padding=ft.padding.only(5, 5, 5, 5), on_click=self.list_item_click, data={'row': row}, key=row.uuid, @@ -883,10 +1026,10 @@ class POSView(WuttaView): def select_txn_item(self, item): if self.selected_item: - self.selected_item.bgcolor = 'white' + self.clear_item_selection() - item.bgcolor = 'blue' self.selected_item = item + self.selected_item.bgcolor = 'blue' self.page.update() def void_click(self, e): @@ -928,9 +1071,9 @@ class POSView(WuttaView): row = session.get(row.__class__, row.uuid) handler.void_row(row, user) self.selected_item.data['row'] = row - self.selected_item.content.mark_void() - self.selected_item.bgcolor = 'white' - self.selected_item = None + self.selected_item.content.row = row + self.selected_item.content.refresh() + self.clear_item_selection() # update screen to reflect new balance self.txn_total.value = self.app.render_currency(batch.get_balance()) @@ -1163,6 +1306,12 @@ class POSView(WuttaView): self.reset() + def clear_item_selection(self): + if self.selected_item: + self.selected_item.bgcolor = 'white' + self.selected_item.content.refresh() + self.selected_item = None + def clear_all(self): self.items.controls.clear() self.txn_total.value = None