From 545f115dc837f9ac1acf702ad2838eb2c2d1a363 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 23 Sep 2023 11:19:42 -0500 Subject: [PATCH] Use actual POS batch under the hood --- wuttapos/app.py | 12 +++ wuttapos/views/base.py | 4 +- wuttapos/views/pos.py | 210 +++++++++++++++++++++++++++++++++++------ 3 files changed, 195 insertions(+), 31 deletions(-) diff --git a/wuttapos/app.py b/wuttapos/app.py index cbab023..8690065 100644 --- a/wuttapos/app.py +++ b/wuttapos/app.py @@ -36,6 +36,8 @@ import wuttapos def main(page: ft.Page): config = make_config() + app = config.get_app() + model = app.model page.title = f"WuttaPOS v{wuttapos.__version__}" page.window_full_screen = True @@ -50,6 +52,16 @@ def main(page: ft.Page): if os.path.exists(path): with open(path, 'rt') as f: page.shared = json.loads(f.read()) + page.shared.pop('txn_display', None) # TODO + if page.shared and page.shared.get('user_uuid'): + handler = app.get_batch_handler('pos') + session = app.make_session() + user = session.get(model.User, page.shared['user_uuid']) + # TODO: should also filter this by terminal + batch = handler.get_current_batch(user, create=False) + if batch: + page.shared['txn_display'] = batch.id_str + session.close() def clean_exit(): if not config.production(): diff --git a/wuttapos/views/base.py b/wuttapos/views/base.py index 641867f..9c78546 100644 --- a/wuttapos/views/base.py +++ b/wuttapos/views/base.py @@ -40,6 +40,7 @@ class WuttaView(ft.View): self.config = config self.app = self.config.get_app() self.model = self.app.model + self.enum = self.app.enum controls = self.build_controls() self.controls = [ @@ -52,7 +53,8 @@ class WuttaView(ft.View): return [self.build_header()] def build_header(self): - return WuttaHeader(self.config) + self.header = WuttaHeader(self.config) + return self.header class WuttaViewContainer(ft.Container): diff --git a/wuttapos/views/pos.py b/wuttapos/views/pos.py index 9084f4a..cfb8b85 100644 --- a/wuttapos/views/pos.py +++ b/wuttapos/views/pos.py @@ -43,9 +43,17 @@ class POSView(WuttaView): self.items = ft.ListView() - self.items_container = ft.Container(content=self.items, - padding=ft.padding.only(10, 0, 10, 0), - expand=1) + self.txn_total = ft.Text("", size=40) + + self.items_column = ft.Column( + controls=[ + ft.Container(content=self.items, + padding=ft.padding.only(10, 0, 10, 0)), + ft.Row([self.txn_total], + alignment=ft.MainAxisAlignment.END), + ], + expand=1, + ) def tenkey_click(e): value = e.control.content.value @@ -131,16 +139,17 @@ class POSView(WuttaView): meta_button_width = meta_button_height * 2 meta_font_size = tenkey_font_size - def meta_button(text): + def meta_button(text, on_click=None, bgcolor='yellow'): return ft.Container(content=ft.Text(text, size=meta_font_size, weight=ft.FontWeight.BOLD), height=meta_button_height, width=meta_button_width, # on_click=meta_click, + on_click=on_click, alignment=ft.alignment.center, border=ft.border.all(1, 'black'), border_radius=ft.border_radius.all(5), - bgcolor='orange') + bgcolor=bgcolor) self.meta_menu = ft.Container( content=ft.Column( @@ -148,7 +157,7 @@ class POSView(WuttaView): ft.Row( [ meta_button("MGR"), - meta_button("VOID"), + meta_button("VOID", bgcolor='red', on_click=self.void_click), ], spacing=0, ), @@ -178,6 +187,36 @@ class POSView(WuttaView): ), expand=0) + context_button_height = tenkey_button_size + context_button_width = context_button_height * 2 + context_font_size = tenkey_font_size + + def context_button(text, on_click=None): + return ft.Container(content=ft.Text(text, size=context_font_size, + weight=ft.FontWeight.BOLD), + height=context_button_height, + width=context_button_width, + on_click=on_click, + alignment=ft.alignment.center, + border=ft.border.all(1, 'black'), + border_radius=ft.border_radius.all(5), + bgcolor='orange') + + self.context_menu = ft.Container( + content=ft.Column( + [ + ft.Row( + [ + context_button("CASH", on_click=self.tender_click), + context_button("CHECK", on_click=self.tender_click), + ], + spacing=0, + ), + ], + spacing=0, + ), + expand=0) + return [ self.build_header(), @@ -192,9 +231,18 @@ class POSView(WuttaView): ft.Row( [ - self.items_container, - self.tenkey_menu, - self.meta_menu, + self.items_column, + ft.Column( + [ + ft.Row( + [ + self.tenkey_menu, + self.meta_menu, + ], + ), + self.context_menu, + ], + ), ], vertical_alignment=ft.CrossAxisAlignment.START, ), @@ -205,29 +253,130 @@ class POSView(WuttaView): kwargs.setdefault('size', 24) return ft.Text(*args, **kwargs) - def main_submit(self, e): - value = self.main_input.value - + def did_mount(self): + model = self.model session = self.app.make_session() - product = self.app.get_products_handler().locate_product_for_entry(session, value) - if product: - price = product.current_price or product.regular_price - pretty_price = self.app.render_currency(price.price) + handler = self.app.get_batch_handler('pos') + user = session.get(model.User, self.page.shared['user_uuid']) + batch = handler.get_current_batch(user, create=True) + self.page.shared['txn_display'] = batch.id_str - self.items.controls.append( - ft.Container( - content=ft.Row( - [ - ft.Row([ - self.make_text(f"{product}"), - self.make_text(f"× 1 @ {pretty_price}", weight=None, italic=True), - ]), - self.make_text(pretty_price), - ], - alignment=ft.MainAxisAlignment.SPACE_BETWEEN, - ), - border=ft.border.only(bottom=ft.border.BorderSide(1, 'gray')), - padding=ft.padding.only(0, 5, 0, 5))) + self.items.controls.clear() + for row in batch.active_rows(): + self.add_row_item(row) + + self.txn_total.value = self.app.render_currency(batch.sales_total) + + session.commit() + session.close() + + def add_row_item(self, row): + quantity = self.app.render_quantity(row.quantity) + pretty_price = self.app.render_currency(row.txn_price) + self.items.controls.append( + ft.Container( + content=ft.Row( + [ + ft.Row([ + self.make_text(f"{row.description}"), + self.make_text(f"× {quantity} @ {pretty_price}", + weight=None, italic=True, size=20), + ]), + self.make_text(pretty_price), + ], + alignment=ft.MainAxisAlignment.SPACE_BETWEEN, + ), + border=ft.border.only(bottom=ft.border.BorderSide(1, 'gray')), + padding=ft.padding.only(0, 5, 0, 5))) + + def void_click(self, e): + + def confirm(e): + dlg.open = False + + model = self.model + session = self.app.make_session() + handler = self.app.get_batch_handler('pos') + user = session.get(model.User, self.page.shared['user_uuid']) + batch = handler.get_current_batch(user, create=True) + + # void current batch + handler.void_batch(batch, user) + session.flush() + + # make new batch + batch = handler.get_current_batch(user, create=True) + + # commit changes + session.commit() + session.close() + + # reset txn display + self.items.controls.clear() + self.txn_total.value = None + self.page.shared['txn_display'] = batch.id_str + self.header.update_txn_display() + # TODO: not clear why must call update() for header too? + self.header.update() + self.page.update() + + def cancel(e): + dlg.open = False + self.page.update() + + dlg = ft.AlertDialog( + modal=True, + title=ft.Text("Confirm VOID"), + content=ft.Text("Really VOID this transaction?"), + actions=[ + ft.TextButton("Yes", on_click=confirm), + ft.TextButton("No", on_click=cancel), + ]) + + self.page.dialog = dlg + dlg.open = True + self.page.update() + + def tender_click(self, e): + model = self.model + session = self.app.make_session() + handler = self.app.get_batch_handler('pos') + user = session.get(model.User, self.page.shared['user_uuid']) + batch = handler.get_current_batch(user) + + # tender / execute batch + tender = e.control.content.value + handler.tender_and_execute(batch, user, tender) + + # make new batch + batch = handler.get_current_batch(user, create=True) + + session.commit() + session.close() + + # reset txn display + self.items.controls.clear() + self.txn_total.value = None + self.page.shared['txn_display'] = batch.id_str + self.header.update_txn_display() + # TODO: not clear why must call update() for header too? + self.header.update() + self.main_input.focus() + self.page.update() + + def main_submit(self, e): + handler = self.app.get_batch_handler('pos') + session = self.app.make_session() + model = self.model + + user = session.get(model.User, self.page.shared['user_uuid']) + batch = handler.get_current_batch(user) + + value = self.main_input.value + row = handler.process_entry(batch, value) + if row: + self.add_row_item(row) + self.txn_total.value = self.app.render_currency(batch.sales_total) else: self.page.snack_bar = ft.SnackBar(ft.Text(f"UNRECOGNIZED: {value}", @@ -237,6 +386,7 @@ class POSView(WuttaView): duration=1500) self.page.snack_bar.open = True + session.commit() session.close() self.main_input.value = ""