wuttapos/wuttapos/views/pos.py
Lance Edgar 51c1094e1c Add basic Feedback button for POS screen
this will definitely need to change, but wanted it on screen at least
2023-09-24 19:29:57 -05:00

1025 lines
39 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8; -*-
################################################################################
#
# WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar
#
# This file is part of WuttaPOS.
#
# WuttaPOS is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
WuttaPOS - POS view
"""
import decimal
import logging
import time
import flet as ft
from .base import WuttaView
from wuttapos.controls.keyboard import WuttaKeyboard
from wuttapos.util import get_pos_batch_handler
log = logging.getLogger(__name__)
class POSView(WuttaView):
"""
Main POS view for WuttaPOS
"""
# TODO: should be configurable?
default_button_size = 100
default_font_size = 40
disabled_bgcolor = '#aaaaaa'
def get_batch_handler(self):
return get_pos_batch_handler(self.config)
def set_customer(self, customer, batch=None):
session = self.app.get_session(customer)
if not batch:
batch = self.get_current_batch(session)
handler = self.get_batch_handler()
handler.set_customer(batch, customer)
key = self.app.get_customer_key_field()
value = getattr(customer, key)
self.page.shared['cust_uuid'] = customer.uuid
self.page.shared['cust_display'] = str(value or '')
self.header.update_cust_display()
self.header.update()
# TODO: can we assume caller will do this?
# self.page.update()
self.page.snack_bar = ft.SnackBar(ft.Text(f"CUSTOMER SET: {customer}",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='green',
duration=1500)
self.page.snack_bar.open = True
def customer_lookup(self):
font_size = self.default_font_size * 0.8
selected_customer_uuid = None
def lookup(e=None):
global selected_customer_uuid
entry = searchbox.value
session = self.app.make_session()
results = self.app.get_clientele_handler().search_customers(session, entry)
search_results.rows.clear()
selected_customer_uuid = None
select_button.disabled = True
select_button.bgcolor = self.disabled_bgcolor
if results:
for customer in results:
search_results.rows.append(ft.DataRow(
cells=[
make_cell(customer['_customer_key_']),
make_cell(customer['name']),
make_cell(customer['phone_number']),
make_cell(customer['email_address']),
],
on_select_changed=select_changed,
data={'uuid': customer['uuid']},
))
no_results.visible = False
else:
no_results.value = f"NO RESULTS FOR: {entry}"
no_results.visible = True
searchbox.value = ''
searchbox.focus()
self.page.update()
searchbox = ft.TextField("", text_size=font_size * 0.8,
on_submit=lookup,
autofocus=True,
expand=True)
def keypress(key):
if key == '':
lookup()
else:
if key == '':
searchbox.value = searchbox.value[:-1]
else:
searchbox.value += key
searchbox.focus()
self.page.update()
def make_cell_text(text):
return ft.Text(text, size=32)
def make_cell(text):
return ft.DataCell(make_cell_text(text))
def reset(e):
global selected_customer_uuid
searchbox.value = ""
search_results.rows.clear()
no_results.visible = False
selected_customer_uuid = None
select_button.disabled = True
select_button.bgcolor = self.disabled_bgcolor
searchbox.focus()
self.page.update()
def select_changed(e):
global selected_customer_uuid
if e.data: # selected
selected_customer_uuid = e.control.data['uuid']
select_button.disabled = False
select_button.bgcolor = 'blue'
e.control.color = ft.colors.BLUE
else:
selected_customer_uuid = None
select_button.disabled = True
select_button.bgcolor = self.disabled_bgcolor
e.control.color = None
self.page.update()
def select_customer(e):
global selected_customer_uuid
if not selected_customer_uuid:
raise RuntimeError("no customer selected?")
session = self.app.make_session()
customer = session.get(self.model.Customer, selected_customer_uuid)
self.set_customer(customer)
session.commit()
session.close()
dlg.open = False
self.main_input.focus()
self.page.update()
def cancel(e):
dlg.open = False
self.main_input.focus()
self.page.update()
search_results = ft.DataTable(
columns=[
ft.DataColumn(make_cell_text(self.app.get_customer_key_label())),
ft.DataColumn(make_cell_text("Name")),
ft.DataColumn(make_cell_text("Phone")),
ft.DataColumn(make_cell_text("Email")),
],
)
no_results = ft.Text("NO RESULTS", size=32, color='red',
weight=ft.FontWeight.BOLD,
visible=False)
select_button = ft.Container(
content=ft.Text("Select", size=font_size * 0.8),
alignment=ft.alignment.center,
height=self.page.data['default_button_height_dlg'] * 0.8,
width=self.page.data['default_button_height_dlg'] * 1.3,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=select_customer,
disabled=True,
bgcolor=self.disabled_bgcolor,
)
dlg = ft.AlertDialog(
modal=True,
title=ft.Text("Customer Lookup"),
content=ft.Column(
[
ft.Row(
[
ft.Text("SEARCH FOR:"),
searchbox,
ft.Container(
content=ft.Text("Lookup", size=font_size * 0.8),
alignment=ft.alignment.center,
height=self.page.data['default_button_height_dlg'] * 0.8,
width=self.page.data['default_button_height_dlg'] * 1.3,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=lookup,
bgcolor='blue',
),
ft.Container(
content=ft.Text("Reset", size=font_size * 0.8),
alignment=ft.alignment.center,
height=self.page.data['default_button_height_dlg'] * 0.8,
width=self.page.data['default_button_height_dlg'] * 1.3,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=reset,
bgcolor='yellow',
),
ft.Container(
content=ft.Text("Cancel", size=font_size * 0.8),
alignment=ft.alignment.center,
height=self.page.data['default_button_height_dlg'] * 0.8,
width=self.page.data['default_button_height_dlg'] * 1.3,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=cancel,
),
],
),
ft.Divider(),
WuttaKeyboard(self.config, on_keypress=keypress),
ft.Divider(),
ft.Row(
[
ft.Column(
[
search_results,
no_results,
],
expand=True,
),
select_button,
],
),
],
),
)
self.page.dialog = dlg
dlg.open = True
self.page.update()
def customer_info(self):
clientele = self.app.get_clientele_handler()
session = self.app.make_session()
entry = self.main_input.value
if entry:
different = True
customer = clientele.locate_customer_for_entry(session, entry)
if not customer:
session.close()
self.page.snack_bar = ft.SnackBar(ft.Text(f"CUSTOMER NOT FOUND: {entry}",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
self.page.update()
return
else:
different = False
customer = session.get(self.model.Customer, self.page.shared['cust_uuid'])
assert customer
info = clientele.get_customer_info_markdown(customer)
session.close()
def close(e):
dlg.open = False
self.main_input.value = ''
self.main_input.focus()
self.page.update()
font_size = self.default_font_size * 0.8
dlg = ft.AlertDialog(
# modal=True,
title=ft.Text("Customer Info"),
content=ft.Container(ft.Column(
[
ft.Container(
content=ft.Text("NOTE: this is a DIFFERENT customer than the txn has!"),
bgcolor='yellow',
visible=different,
),
ft.Markdown(info),
],
height=500,
width=500,
)),
actions=[
ft.Container(content=ft.Text("Close",
size=font_size,
weight=ft.FontWeight.BOLD),
height=self.default_button_size * 0.8,
width=self.default_button_size * 1.2,
alignment=ft.alignment.center,
bgcolor='blue',
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=close),
],
# actions_alignment=ft.MainAxisAlignment.END,
)
self.page.dialog = dlg
dlg.open = True
self.page.update()
def customer_prompt(self):
def view_info(e):
dlg.open = False
self.page.update()
# cf. https://github.com/flet-dev/flet/issues/1670
time.sleep(0.1)
self.customer_info()
def replace(e):
dlg.open = False
self.page.update()
# cf. https://github.com/flet-dev/flet/issues/1670
time.sleep(0.1)
entry = self.main_input.value
if entry:
self.attempt_set_customer(entry)
else:
self.customer_lookup()
def cancel(e):
dlg.open = False
self.main_input.value = ''
self.main_input.focus()
self.page.update()
font_size = self.default_font_size * 0.8
dlg = ft.AlertDialog(
# modal=True,
title=ft.Text("Customer Already Selected"),
content=ft.Text("What would you like to do?", size=20),
actions=[
ft.Container(content=ft.Text("View Info",
size=font_size,
weight=ft.FontWeight.BOLD),
height=self.default_button_size,
width=self.default_button_size * 2.5,
alignment=ft.alignment.center,
bgcolor='blue',
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=view_info),
ft.Container(content=ft.Text("Replace",
size=font_size,
color='black',
weight=ft.FontWeight.BOLD),
height=self.default_button_size,
width=self.default_button_size * 2.5,
alignment=ft.alignment.center,
bgcolor='yellow',
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=replace),
ft.Container(content=ft.Text("Cancel",
size=font_size,
# color='black',
weight=ft.FontWeight.BOLD),
height=self.default_button_size,
width=self.default_button_size * 2.5,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=cancel),
])
self.page.dialog = dlg
dlg.open = True
self.page.update()
def attempt_set_customer(self, entry=None):
session = self.app.make_session()
customer = self.app.get_clientele_handler().locate_customer_for_entry(session, entry)
if customer:
self.set_customer(customer)
self.main_input.value = ''
self.main_input.focus()
self.page.update()
else: # customer not found
self.page.snack_bar = ft.SnackBar(ft.Text(f"CUSTOMER NOT FOUND: {entry}",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
self.main_input.focus()
self.page.update()
session.commit()
session.close()
def customer_click(self, e):
# prompt user to replace customer if already set
if self.page.shared['cust_uuid']:
self.customer_prompt()
else:
value = self.main_input.value
if value:
# okay try to set it with given value
self.attempt_set_customer(value)
else:
# no value provided, so do lookup
self.customer_lookup()
def build_controls(self):
self.main_input = ft.TextField(on_submit=self.main_submit,
text_size=24,
text_style=ft.TextStyle(weight=ft.FontWeight.BOLD),
autofocus=True)
self.items = ft.ListView(
height=800,
)
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 feedback_click(e):
message = ft.TextField(label="Message",
multiline=True, min_lines=5,
autofocus=True)
def send_feedback(e):
self.app.send_email('pos_feedback', data={
'user_name': self.page.shared['user_display'],
'message': message.value,
})
dlg.open = False
self.page.snack_bar = ft.SnackBar(ft.Text(f"MESSAGE WAS SENT",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='green',
duration=1500)
self.page.snack_bar.open = True
self.main_input.focus()
self.page.update()
def cancel(e):
dlg.open = False
self.main_input.focus()
self.page.update()
button_height = self.default_button_size * 0.8
dlg = ft.AlertDialog(
modal=True,
title=ft.Text("User Feedback"),
content=ft.Container(
content=ft.Column(
[
ft.Text("Questions, suggestions, comments, complaints, etc. "
"are welcome and may be submitted below. "),
ft.Divider(),
message,
],
expand=True,
),
height=500,
),
actions=[
ft.Row(
[
ft.Container(content=ft.Text("Send Message",
size=self.default_font_size,
color='black',
weight=ft.FontWeight.BOLD),
height=button_height,
width=self.default_button_size * 3,
alignment=ft.alignment.center,
bgcolor='blue',
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=send_feedback),
ft.Container(content=ft.Text("Cancel",
size=self.default_font_size,
weight=ft.FontWeight.BOLD),
height=button_height,
width=self.default_button_size * 2.5,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=cancel),
],
alignment=ft.MainAxisAlignment.CENTER,
),
],
)
self.page.dialog = dlg
dlg.open = True
self.page.update()
def tenkey_click(e):
value = e.control.content.value
if value == 'ENTER':
self.main_submit()
elif value == '': # backspace
if self.main_input.value:
self.main_input.value = self.main_input.value[:-1]
self.main_input.focus()
self.page.update()
elif value == 'CE': # clear entry
if self.main_input.value:
self.main_input.value = ""
elif self.set_quantity.data is not None:
self.set_quantity.data = None
self.set_quantity.value = None
self.main_input.focus()
self.page.update()
elif value == '@':
quantity = self.main_input.value
valid = False
if self.set_quantity.data is not None:
quantity = self.set_quantity.data
self.page.snack_bar = ft.SnackBar(ft.Text(f"QUANTITY ALREADY SET: {quantity}",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
else:
try:
quantity = decimal.Decimal(quantity)
valid = True
except decimal.InvalidOperation:
pass
if valid and quantity:
self.set_quantity.data = quantity
self.set_quantity.value = self.app.render_quantity(quantity) + " @ "
self.main_input.value = ""
self.main_input.focus()
else:
self.page.snack_bar = ft.SnackBar(ft.Text(f"INVALID @ QUANTITY: {quantity}",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
self.page.update()
elif value == '':
self.items.scroll_to(delta=-50, duration=250)
self.page.update()
elif value == '':
self.items.scroll_to(delta=50, duration=250)
self.page.update()
else:
self.main_input.value = f"{self.main_input.value or ''}{value}"
self.main_input.focus()
self.page.update()
def up_long_press(e):
self.items.scroll_to(delta=-500, duration=250)
self.page.update()
def down_long_press(e):
self.items.scroll_to(delta=500, duration=250)
self.page.update()
tenkey_button_size = self.default_button_size
tenkey_font_size = self.default_font_size
def tenkey_button(text,
bgcolor='green',
height=tenkey_button_size,
width=tenkey_button_size,
on_click=tenkey_click,
on_long_press=None,
):
return ft.Container(content=ft.Text(text, size=tenkey_font_size,
weight=ft.FontWeight.BOLD),
height=height,
width=width,
on_click=on_click,
on_long_press=on_long_press,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
bgcolor=bgcolor)
self.tenkey_menu = ft.Container(
content=ft.Column(
[
ft.Row(
[
tenkey_button("1"),
tenkey_button("2"),
tenkey_button("3"),
tenkey_button("@"),
],
spacing=0,
),
ft.Row(
[
tenkey_button("4"),
tenkey_button("5"),
tenkey_button("6"),
tenkey_button("", on_long_press=up_long_press),
],
spacing=0,
),
ft.Row(
[
tenkey_button("7"),
tenkey_button("8"),
tenkey_button("9"),
tenkey_button("", on_long_press=down_long_press),
],
spacing=0,
),
ft.Row(
[
tenkey_button("0"),
# tenkey_button("00"),
tenkey_button("."),
tenkey_button("ENTER", width=tenkey_button_size * 2),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
spacing=0,
),
],
spacing=0,
),
expand=0)
meta_button_height = tenkey_button_size
meta_button_width = meta_button_height * 2
meta_font_size = tenkey_font_size
def meta_button(text, on_click=None, bgcolor='blue'):
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=on_click,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
bgcolor=bgcolor)
self.meta_menu = ft.Container(
content=ft.Column(
[
ft.Row(
[
meta_button("MGR", bgcolor='blue', on_click=self.not_supported),
meta_button("VOID", bgcolor='red', on_click=self.void_click),
],
spacing=0,
),
ft.Row(
[
meta_button("ITEM", bgcolor='blue', on_click=self.not_supported),
meta_button("CUST", bgcolor='blue', on_click=self.customer_click),
],
spacing=0,
),
ft.Row(
[
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
],
spacing=0,
),
ft.Row(
[
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
],
spacing=0,
),
],
spacing=0,
),
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)
self.set_quantity = ft.Text(value=None, data=None, weight=ft.FontWeight.BOLD, size=40)
return [
self.build_header(),
ft.Row(
[
tenkey_button("Feedback", height=70, width=200,
bgcolor='blue', on_click=feedback_click),
ft.Row(
[
self.set_quantity,
self.main_input,
tenkey_button("", height=70, width=70),
tenkey_button("CE", height=70, width=70),
],
alignment=ft.MainAxisAlignment.CENTER,
expand=True,
),
],
),
ft.Row(),
ft.Row(),
ft.Row(),
ft.Row(
[
self.items_column,
ft.Column(
[
ft.Row(
[
self.tenkey_menu,
self.meta_menu,
],
),
self.context_menu,
],
),
],
vertical_alignment=ft.CrossAxisAlignment.START,
),
]
def make_text(self, *args, **kwargs):
kwargs.setdefault('weight', ft.FontWeight.BOLD)
kwargs.setdefault('size', 24)
return ft.Text(*args, **kwargs)
def get_current_batch(self, session, user=None, create=True):
if not user:
user = session.get(self.model.User, self.page.shared['user_uuid'])
handler = self.get_batch_handler()
return handler.get_current_batch(user, create=create)
def did_mount(self):
session = self.app.make_session()
batch = self.get_current_batch(session)
self.page.shared['txn_display'] = batch.id_str
self.items.controls.clear()
for row in batch.active_rows():
self.add_row_item(row)
self.items.scroll_to(offset=-1, duration=100)
self.txn_total.value = self.app.render_currency(batch.sales_total)
session.commit()
session.close()
self.page.update()
def not_supported(self, e=None, feature=None):
text = "NOT YET SUPPORTED"
if not feature and e:
feature = e.control.content.value
if feature:
text += f": {feature}"
self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black',
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
self.page.update()
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(self.app.render_currency(row.sales_total)),
],
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.get_batch_handler()
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()
self.clear_all()
# make new batch
batch = handler.get_current_batch(user, create=True)
self.page.shared['txn_display'] = batch.id_str
session.commit()
session.close()
self.main_input.focus()
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.Container(content=ft.Text("Yes, VOID",
size=self.default_font_size,
color='black',
weight=ft.FontWeight.BOLD),
height=self.default_button_size,
width=self.default_button_size * 2.5,
alignment=ft.alignment.center,
bgcolor='red',
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
on_click=confirm),
ft.Container(content=ft.Text("Cancel",
size=self.default_font_size,
weight=ft.FontWeight.BOLD),
height=self.default_button_size,
width=self.default_button_size * 2.5,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
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.get_batch_handler()
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)
self.clear_all()
# make new batch
batch = handler.get_current_batch(user, create=True)
self.page.shared['txn_display'] = batch.id_str
self.header.update_txn_display()
self.header.update()
session.commit()
session.close()
self.main_input.focus()
self.page.update()
def clear_all(self):
self.items.controls.clear()
self.txn_total.value = None
self.page.shared['txn_display'] = None
self.page.shared['cust_uuid'] = None
self.page.shared['cust_display'] = None
self.header.update_txn_display()
self.header.update_cust_display()
# TODO: not clear why must call update() for header too?
self.header.update()
def main_submit(self, e=None):
value = self.main_input.value
if not value:
self.main_input.focus()
self.items.scroll_to(offset=-1, duration=250)
self.page.update()
return
handler = self.get_batch_handler()
session = self.app.make_session()
model = self.model
user = session.get(model.User, self.page.shared['user_uuid'])
batch = handler.get_current_batch(user)
kwargs = {}
if self.set_quantity.data is not None:
kwargs['quantity'] = self.set_quantity.data
row = handler.process_entry(batch, value, **kwargs)
if row:
self.add_row_item(row)
self.items.scroll_to(offset=-1, duration=250)
self.txn_total.value = self.app.render_currency(batch.sales_total)
else:
self.page.snack_bar = ft.SnackBar(ft.Text(f"UNRECOGNIZED: {value}",
color='black',
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
session.commit()
session.close()
self.main_input.value = ""
self.main_input.focus()
self.set_quantity.data = None
self.set_quantity.value = None
self.page.update()