Add basic customer lookup

This commit is contained in:
Lance Edgar 2023-09-23 20:16:24 -05:00
parent 545f115dc8
commit 8f937d040a
4 changed files with 803 additions and 56 deletions

View file

@ -43,6 +43,12 @@ def main(page: ft.Page):
page.window_full_screen = True
# page.vertical_alignment = ft.MainAxisAlignment.CENTER
# global defaults for button/text styles etc.
page.data = {
'default_button_height_pos': 100,
'default_button_height_dlg': 80,
}
# nb. track current user, txn etc.
page.shared = {}
@ -53,6 +59,8 @@ def main(page: ft.Page):
with open(path, 'rt') as f:
page.shared = json.loads(f.read())
page.shared.pop('txn_display', None) # TODO
page.shared.pop('cust_uuid', None) # TODO
page.shared.pop('cust_display', None) # TODO
if page.shared and page.shared.get('user_uuid'):
handler = app.get_batch_handler('pos')
session = app.make_session()
@ -61,6 +69,13 @@ def main(page: ft.Page):
batch = handler.get_current_batch(user, create=False)
if batch:
page.shared['txn_display'] = batch.id_str
page.shared['cust_uuid'] = batch.customer_uuid
if batch.customer:
key = app.get_customer_key_field()
value = getattr(batch.customer, key)
page.shared['cust_display'] = str(value or '') or None
else:
page.shared['cust_display'] = None
session.close()
def clean_exit():

View file

@ -34,6 +34,7 @@ class WuttaHeader(WuttaControl):
def build(self):
self.txn_display = ft.Text("Txn: N", weight=ft.FontWeight.BOLD, size=20)
self.cust_display = ft.Text("Cust: N", weight=ft.FontWeight.BOLD, size=20)
self.user_display = ft.Text("User: N", weight=ft.FontWeight.BOLD, size=20)
self.logout_button = ft.FilledButton("Logout", on_click=self.logout_click, visible=False)
self.logout_divider = ft.VerticalDivider(visible=False)
@ -41,7 +42,7 @@ class WuttaHeader(WuttaControl):
controls = [
self.txn_display,
ft.VerticalDivider(),
ft.Text(f"Cust: N", weight=ft.FontWeight.BOLD, size=20),
self.cust_display,
ft.VerticalDivider(),
WuttaTimestamp(self.config, expand=True,
weight=ft.FontWeight.BOLD, size=20),
@ -56,6 +57,7 @@ class WuttaHeader(WuttaControl):
def did_mount(self):
self.update_txn_display()
self.update_cust_display()
self.update_user_display()
self.update()
@ -67,6 +69,14 @@ class WuttaHeader(WuttaControl):
self.txn_display.value = f"Txn: {txn_display}"
def update_cust_display(self):
cust_display = "N"
if self.page and self.page.shared and self.page.shared.get('cust_display'):
cust_display = self.page.shared['cust_display']
self.cust_display.value = f"Cust: {cust_display}"
def update_user_display(self):
user_display = "N"
@ -84,5 +94,6 @@ class WuttaHeader(WuttaControl):
'user_uuid': None,
'user_display': None,
'txn_display': None,
'cust_display': None,
})
self.page.go('/login')

View file

@ -0,0 +1,179 @@
# -*- 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 - keyboard control
"""
import flet as ft
from .base import WuttaControl
class WuttaKeyboard(WuttaControl):
default_font_size = 20
default_button_size = 80
def __init__(self, *args, **kwargs):
self.on_keypress = kwargs.pop('on_keypress', None)
super().__init__(*args, **kwargs)
self.caps_lock = False
self.caps_map = dict([(k, k.upper())
for k in 'abcdefghijklmnopqrstuvwxyz'])
self.shift = False
self.shift_map = {
'`': '~',
'1': '!',
'2': '@',
'3': '#',
'4': '$',
'5': '%',
'6': '^',
'7': '&',
'8': '*',
'9': '(',
'0': ')',
'-': '_',
'=': '+',
'[': '{',
']': '}',
'\\': '|',
';': ':',
"'": '"',
',': '<',
'.': '>',
'/': '?',
}
def update_caps_lock(self, caps_lock):
self.caps_lock = caps_lock
if self.caps_lock:
self.caps_key.bgcolor = 'blue'
else:
self.caps_key.bgcolor = None
for key, button in self.keys.items():
if key in self.caps_map:
if self.caps_lock:
button.content.value = self.caps_map[key]
else:
button.content.value = key
self.update()
def update_shift(self, shift):
self.shift = shift
if self.shift:
self.shift_key.bgcolor = 'blue'
else:
self.shift_key.bgcolor = None
for key, button in self.keys.items():
if key in self.caps_map:
if self.shift:
button.content.value = self.caps_map[key]
else:
button.content.value = key
elif key in self.shift_map:
if self.shift:
button.content.value = self.shift_map[key]
else:
button.content.value = key
self.update()
def simple_keypress(self, e):
# maybe inform parent
if self.on_keypress:
key = e.control.content.value
# avoid callback for certain keys
if key not in ('CAPS', 'SHIFT'):
# translate certain keys
if key == 'SPACE':
key = ' '
# let 'em know
self.on_keypress(key)
# turn off shift key if set
if self.shift:
self.update_shift(False)
def build(self):
self.keys = {}
def make_key(key, data=None, on_click=self.simple_keypress,
width=self.default_button_size,
bgcolor=None):
button = ft.Container(content=ft.Text(key, size=self.default_font_size,
weight=ft.FontWeight.BOLD),
data=data or key,
height=self.default_button_size,
width=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.keys[key] = button
return button
def caps_click(e):
self.update_caps_lock(not self.caps_lock)
self.caps_key = make_key('CAPS', on_click=caps_click)
if self.caps_lock:
self.caps_key.bgcolor = 'blue'
def shift_click(e):
self.update_shift(not self.shift)
self.shift_key = make_key('SHIFT', on_click=shift_click)
if self.shift:
self.shift_key.bgcolor = 'blue'
rows = [
[make_key(k) for k in "`1234567890-="] + [make_key('', bgcolor='yellow')],
[make_key(k) for k in "qwertyuiop[]\\"],
[self.caps_key] + [make_key(k) for k in "asdfghjkl;'"] + [make_key('', bgcolor='blue')],
[self.shift_key] + [make_key(k) for k in "zxcvbnm,./"],
[make_key('SPACE', width=self.default_button_size * 5)],
]
rows = [ft.Row(controls, alignment=ft.MainAxisAlignment.CENTER)
for controls in rows]
return ft.Container(
content=ft.Column(
rows,
),
)

View file

@ -24,9 +24,16 @@
WuttaPOS - POS view
"""
import decimal
import logging
import time
import flet as ft
from .base import WuttaView
from wuttapos.controls.keyboard import WuttaKeyboard
log = logging.getLogger(__name__)
class POSView(WuttaView):
@ -34,6 +41,415 @@ 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 self.app.get_batch_handler('pos')
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,
@ -41,14 +457,17 @@ class POSView(WuttaView):
text_style=ft.TextStyle(weight=ft.FontWeight.BOLD),
autofocus=True)
self.items = ft.ListView()
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.Container(
content=self.items,
padding=ft.padding.only(10, 0, 10, 0)),
ft.Row([self.txn_total],
alignment=ft.MainAxisAlignment.END),
],
@ -59,37 +478,99 @@ class POSView(WuttaView):
value = e.control.content.value
if value == 'ENTER':
print('TODO: handle enter')
self.main_submit()
elif value == '': # backspace
if self.main_input.value:
self.main_input.value = self.main_input.value[:-1]
self.page.update()
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 == '':
pass # TODO
self.items.scroll_to(delta=-50, duration=250)
self.page.update()
elif value == '':
pass # TODO
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()
# TODO: should be configurable?
tenkey_button_size = 100
tenkey_font_size = 40
def up_long_press(e):
self.items.scroll_to(delta=-500, duration=250)
self.page.update()
def tenkey_button(text, width=tenkey_button_size):
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_long_press=None,
):
return ft.Container(content=ft.Text(text, size=tenkey_font_size,
weight=ft.FontWeight.BOLD),
height=tenkey_button_size,
height=height,
width=width,
on_click=tenkey_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='green')
bgcolor=bgcolor)
self.tenkey_menu = ft.Container(
content=ft.Column(
@ -99,7 +580,7 @@ class POSView(WuttaView):
tenkey_button("1"),
tenkey_button("2"),
tenkey_button("3"),
tenkey_button(""),
tenkey_button("@"),
],
spacing=0,
),
@ -108,7 +589,7 @@ class POSView(WuttaView):
tenkey_button("4"),
tenkey_button("5"),
tenkey_button("6"),
tenkey_button(""),
tenkey_button("", on_long_press=up_long_press),
],
spacing=0,
),
@ -117,14 +598,15 @@ class POSView(WuttaView):
tenkey_button("7"),
tenkey_button("8"),
tenkey_button("9"),
tenkey_button(""),
tenkey_button("", on_long_press=down_long_press),
],
spacing=0,
),
ft.Row(
[
tenkey_button("0"),
tenkey_button("00"),
# tenkey_button("00"),
tenkey_button("."),
tenkey_button("ENTER", width=tenkey_button_size * 2),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
@ -139,12 +621,11 @@ class POSView(WuttaView):
meta_button_width = meta_button_height * 2
meta_font_size = tenkey_font_size
def meta_button(text, on_click=None, bgcolor='yellow'):
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=meta_click,
on_click=on_click,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
@ -156,29 +637,29 @@ class POSView(WuttaView):
[
ft.Row(
[
meta_button("MGR"),
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"),
meta_button("CUST"),
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"),
meta_button("TODO"),
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"),
meta_button("TODO"),
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
],
spacing=0,
),
@ -217,11 +698,18 @@ class POSView(WuttaView):
),
expand=0)
self.set_quantity = ft.Text(value=None, data=None, weight=ft.FontWeight.BOLD, size=40)
return [
self.build_header(),
ft.Row(
[self.main_input],
[
self.set_quantity,
self.main_input,
tenkey_button("", height=70, width=70),
tenkey_button("CE", height=70, width=70),
],
alignment=ft.MainAxisAlignment.CENTER,
),
@ -253,22 +741,41 @@ class POSView(WuttaView):
kwargs.setdefault('size', 24)
return ft.Text(*args, **kwargs)
def did_mount(self):
model = self.model
session = self.app.make_session()
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.app.get_batch_handler('pos')
user = session.get(model.User, self.page.shared['user_uuid'])
batch = handler.get_current_batch(user, create=True)
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)
@ -282,12 +789,14 @@ class POSView(WuttaView):
self.make_text(f"× {quantity} @ {pretty_price}",
weight=None, italic=True, size=20),
]),
self.make_text(pretty_price),
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)))
padding=ft.padding.only(0, 5, 0, 5),
))
def void_click(self, e):
@ -303,21 +812,16 @@ class POSView(WuttaView):
# 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
# 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.main_input.focus()
self.page.update()
def cancel(e):
@ -325,12 +829,30 @@ class POSView(WuttaView):
self.page.update()
dlg = ft.AlertDialog(
modal=True,
# 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),
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
@ -347,24 +869,39 @@ class POSView(WuttaView):
# 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()
# 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):
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.app.get_batch_handler('pos')
session = self.app.make_session()
model = self.model
@ -372,10 +909,13 @@ class POSView(WuttaView):
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)
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:
@ -391,4 +931,6 @@ class POSView(WuttaView):
self.main_input.value = ""
self.main_input.focus()
self.set_quantity.data = None
self.set_quantity.value = None
self.page.update()