Add basic customer lookup
This commit is contained in:
parent
545f115dc8
commit
8f937d040a
|
@ -43,6 +43,12 @@ def main(page: ft.Page):
|
||||||
page.window_full_screen = True
|
page.window_full_screen = True
|
||||||
# page.vertical_alignment = ft.MainAxisAlignment.CENTER
|
# 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.
|
# nb. track current user, txn etc.
|
||||||
page.shared = {}
|
page.shared = {}
|
||||||
|
|
||||||
|
@ -53,6 +59,8 @@ def main(page: ft.Page):
|
||||||
with open(path, 'rt') as f:
|
with open(path, 'rt') as f:
|
||||||
page.shared = json.loads(f.read())
|
page.shared = json.loads(f.read())
|
||||||
page.shared.pop('txn_display', None) # TODO
|
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'):
|
if page.shared and page.shared.get('user_uuid'):
|
||||||
handler = app.get_batch_handler('pos')
|
handler = app.get_batch_handler('pos')
|
||||||
session = app.make_session()
|
session = app.make_session()
|
||||||
|
@ -61,6 +69,13 @@ def main(page: ft.Page):
|
||||||
batch = handler.get_current_batch(user, create=False)
|
batch = handler.get_current_batch(user, create=False)
|
||||||
if batch:
|
if batch:
|
||||||
page.shared['txn_display'] = batch.id_str
|
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()
|
session.close()
|
||||||
|
|
||||||
def clean_exit():
|
def clean_exit():
|
||||||
|
|
|
@ -34,6 +34,7 @@ class WuttaHeader(WuttaControl):
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
self.txn_display = ft.Text("Txn: N", weight=ft.FontWeight.BOLD, size=20)
|
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.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_button = ft.FilledButton("Logout", on_click=self.logout_click, visible=False)
|
||||||
self.logout_divider = ft.VerticalDivider(visible=False)
|
self.logout_divider = ft.VerticalDivider(visible=False)
|
||||||
|
@ -41,7 +42,7 @@ class WuttaHeader(WuttaControl):
|
||||||
controls = [
|
controls = [
|
||||||
self.txn_display,
|
self.txn_display,
|
||||||
ft.VerticalDivider(),
|
ft.VerticalDivider(),
|
||||||
ft.Text(f"Cust: N", weight=ft.FontWeight.BOLD, size=20),
|
self.cust_display,
|
||||||
ft.VerticalDivider(),
|
ft.VerticalDivider(),
|
||||||
WuttaTimestamp(self.config, expand=True,
|
WuttaTimestamp(self.config, expand=True,
|
||||||
weight=ft.FontWeight.BOLD, size=20),
|
weight=ft.FontWeight.BOLD, size=20),
|
||||||
|
@ -56,6 +57,7 @@ class WuttaHeader(WuttaControl):
|
||||||
|
|
||||||
def did_mount(self):
|
def did_mount(self):
|
||||||
self.update_txn_display()
|
self.update_txn_display()
|
||||||
|
self.update_cust_display()
|
||||||
self.update_user_display()
|
self.update_user_display()
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
@ -67,6 +69,14 @@ class WuttaHeader(WuttaControl):
|
||||||
|
|
||||||
self.txn_display.value = f"Txn: {txn_display}"
|
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):
|
def update_user_display(self):
|
||||||
user_display = "N"
|
user_display = "N"
|
||||||
|
|
||||||
|
@ -84,5 +94,6 @@ class WuttaHeader(WuttaControl):
|
||||||
'user_uuid': None,
|
'user_uuid': None,
|
||||||
'user_display': None,
|
'user_display': None,
|
||||||
'txn_display': None,
|
'txn_display': None,
|
||||||
|
'cust_display': None,
|
||||||
})
|
})
|
||||||
self.page.go('/login')
|
self.page.go('/login')
|
||||||
|
|
179
wuttapos/controls/keyboard.py
Normal file
179
wuttapos/controls/keyboard.py
Normal 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,
|
||||||
|
),
|
||||||
|
)
|
|
@ -24,9 +24,16 @@
|
||||||
WuttaPOS - POS view
|
WuttaPOS - POS view
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import decimal
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
import flet as ft
|
import flet as ft
|
||||||
|
|
||||||
from .base import WuttaView
|
from .base import WuttaView
|
||||||
|
from wuttapos.controls.keyboard import WuttaKeyboard
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class POSView(WuttaView):
|
class POSView(WuttaView):
|
||||||
|
@ -34,6 +41,415 @@ class POSView(WuttaView):
|
||||||
Main POS view for WuttaPOS
|
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):
|
def build_controls(self):
|
||||||
|
|
||||||
self.main_input = ft.TextField(on_submit=self.main_submit,
|
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),
|
text_style=ft.TextStyle(weight=ft.FontWeight.BOLD),
|
||||||
autofocus=True)
|
autofocus=True)
|
||||||
|
|
||||||
self.items = ft.ListView()
|
self.items = ft.ListView(
|
||||||
|
height=800,
|
||||||
|
)
|
||||||
|
|
||||||
self.txn_total = ft.Text("", size=40)
|
self.txn_total = ft.Text("", size=40)
|
||||||
|
|
||||||
self.items_column = ft.Column(
|
self.items_column = ft.Column(
|
||||||
controls=[
|
controls=[
|
||||||
ft.Container(content=self.items,
|
ft.Container(
|
||||||
padding=ft.padding.only(10, 0, 10, 0)),
|
content=self.items,
|
||||||
|
padding=ft.padding.only(10, 0, 10, 0)),
|
||||||
ft.Row([self.txn_total],
|
ft.Row([self.txn_total],
|
||||||
alignment=ft.MainAxisAlignment.END),
|
alignment=ft.MainAxisAlignment.END),
|
||||||
],
|
],
|
||||||
|
@ -59,37 +478,99 @@ class POSView(WuttaView):
|
||||||
value = e.control.content.value
|
value = e.control.content.value
|
||||||
|
|
||||||
if value == 'ENTER':
|
if value == 'ENTER':
|
||||||
print('TODO: handle enter')
|
self.main_submit()
|
||||||
|
|
||||||
elif value == '⌫': # backspace
|
elif value == '⌫': # backspace
|
||||||
if self.main_input.value:
|
if self.main_input.value:
|
||||||
self.main_input.value = self.main_input.value[:-1]
|
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 == '↑':
|
elif value == '↑':
|
||||||
pass # TODO
|
self.items.scroll_to(delta=-50, duration=250)
|
||||||
|
self.page.update()
|
||||||
|
|
||||||
elif value == '↓':
|
elif value == '↓':
|
||||||
pass # TODO
|
self.items.scroll_to(delta=50, duration=250)
|
||||||
|
self.page.update()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.main_input.value = f"{self.main_input.value or ''}{value}"
|
self.main_input.value = f"{self.main_input.value or ''}{value}"
|
||||||
|
self.main_input.focus()
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
# TODO: should be configurable?
|
def up_long_press(e):
|
||||||
tenkey_button_size = 100
|
self.items.scroll_to(delta=-500, duration=250)
|
||||||
tenkey_font_size = 40
|
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,
|
return ft.Container(content=ft.Text(text, size=tenkey_font_size,
|
||||||
weight=ft.FontWeight.BOLD),
|
weight=ft.FontWeight.BOLD),
|
||||||
height=tenkey_button_size,
|
height=height,
|
||||||
width=width,
|
width=width,
|
||||||
on_click=tenkey_click,
|
on_click=tenkey_click,
|
||||||
|
on_long_press=on_long_press,
|
||||||
alignment=ft.alignment.center,
|
alignment=ft.alignment.center,
|
||||||
border=ft.border.all(1, 'black'),
|
border=ft.border.all(1, 'black'),
|
||||||
border_radius=ft.border_radius.all(5),
|
border_radius=ft.border_radius.all(5),
|
||||||
bgcolor='green')
|
bgcolor=bgcolor)
|
||||||
|
|
||||||
self.tenkey_menu = ft.Container(
|
self.tenkey_menu = ft.Container(
|
||||||
content=ft.Column(
|
content=ft.Column(
|
||||||
|
@ -99,7 +580,7 @@ class POSView(WuttaView):
|
||||||
tenkey_button("1"),
|
tenkey_button("1"),
|
||||||
tenkey_button("2"),
|
tenkey_button("2"),
|
||||||
tenkey_button("3"),
|
tenkey_button("3"),
|
||||||
tenkey_button("⌫"),
|
tenkey_button("@"),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
|
@ -108,7 +589,7 @@ class POSView(WuttaView):
|
||||||
tenkey_button("4"),
|
tenkey_button("4"),
|
||||||
tenkey_button("5"),
|
tenkey_button("5"),
|
||||||
tenkey_button("6"),
|
tenkey_button("6"),
|
||||||
tenkey_button("↑"),
|
tenkey_button("↑", on_long_press=up_long_press),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
|
@ -117,14 +598,15 @@ class POSView(WuttaView):
|
||||||
tenkey_button("7"),
|
tenkey_button("7"),
|
||||||
tenkey_button("8"),
|
tenkey_button("8"),
|
||||||
tenkey_button("9"),
|
tenkey_button("9"),
|
||||||
tenkey_button("↓"),
|
tenkey_button("↓", on_long_press=down_long_press),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
ft.Row(
|
ft.Row(
|
||||||
[
|
[
|
||||||
tenkey_button("0"),
|
tenkey_button("0"),
|
||||||
tenkey_button("00"),
|
# tenkey_button("00"),
|
||||||
|
tenkey_button("."),
|
||||||
tenkey_button("ENTER", width=tenkey_button_size * 2),
|
tenkey_button("ENTER", width=tenkey_button_size * 2),
|
||||||
],
|
],
|
||||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||||
|
@ -139,12 +621,11 @@ class POSView(WuttaView):
|
||||||
meta_button_width = meta_button_height * 2
|
meta_button_width = meta_button_height * 2
|
||||||
meta_font_size = tenkey_font_size
|
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,
|
return ft.Container(content=ft.Text(text, size=meta_font_size,
|
||||||
weight=ft.FontWeight.BOLD),
|
weight=ft.FontWeight.BOLD),
|
||||||
height=meta_button_height,
|
height=meta_button_height,
|
||||||
width=meta_button_width,
|
width=meta_button_width,
|
||||||
# on_click=meta_click,
|
|
||||||
on_click=on_click,
|
on_click=on_click,
|
||||||
alignment=ft.alignment.center,
|
alignment=ft.alignment.center,
|
||||||
border=ft.border.all(1, 'black'),
|
border=ft.border.all(1, 'black'),
|
||||||
|
@ -156,29 +637,29 @@ class POSView(WuttaView):
|
||||||
[
|
[
|
||||||
ft.Row(
|
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),
|
meta_button("VOID", bgcolor='red', on_click=self.void_click),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
ft.Row(
|
ft.Row(
|
||||||
[
|
[
|
||||||
meta_button("ITEM"),
|
meta_button("ITEM", bgcolor='blue', on_click=self.not_supported),
|
||||||
meta_button("CUST"),
|
meta_button("CUST", bgcolor='blue', on_click=self.customer_click),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
ft.Row(
|
ft.Row(
|
||||||
[
|
[
|
||||||
meta_button("TODO"),
|
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
||||||
meta_button("TODO"),
|
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
ft.Row(
|
ft.Row(
|
||||||
[
|
[
|
||||||
meta_button("TODO"),
|
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
||||||
meta_button("TODO"),
|
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
|
@ -217,11 +698,18 @@ class POSView(WuttaView):
|
||||||
),
|
),
|
||||||
expand=0)
|
expand=0)
|
||||||
|
|
||||||
|
self.set_quantity = ft.Text(value=None, data=None, weight=ft.FontWeight.BOLD, size=40)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
self.build_header(),
|
self.build_header(),
|
||||||
|
|
||||||
ft.Row(
|
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,
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -253,22 +741,41 @@ class POSView(WuttaView):
|
||||||
kwargs.setdefault('size', 24)
|
kwargs.setdefault('size', 24)
|
||||||
return ft.Text(*args, **kwargs)
|
return ft.Text(*args, **kwargs)
|
||||||
|
|
||||||
def did_mount(self):
|
def get_current_batch(self, session, user=None, create=True):
|
||||||
model = self.model
|
if not user:
|
||||||
session = self.app.make_session()
|
user = session.get(self.model.User, self.page.shared['user_uuid'])
|
||||||
handler = self.app.get_batch_handler('pos')
|
handler = self.app.get_batch_handler('pos')
|
||||||
user = session.get(model.User, self.page.shared['user_uuid'])
|
return handler.get_current_batch(user, create=create)
|
||||||
batch = handler.get_current_batch(user, create=True)
|
|
||||||
|
def did_mount(self):
|
||||||
|
session = self.app.make_session()
|
||||||
|
batch = self.get_current_batch(session)
|
||||||
self.page.shared['txn_display'] = batch.id_str
|
self.page.shared['txn_display'] = batch.id_str
|
||||||
|
|
||||||
self.items.controls.clear()
|
self.items.controls.clear()
|
||||||
for row in batch.active_rows():
|
for row in batch.active_rows():
|
||||||
self.add_row_item(row)
|
self.add_row_item(row)
|
||||||
|
|
||||||
|
self.items.scroll_to(offset=-1, duration=100)
|
||||||
|
|
||||||
self.txn_total.value = self.app.render_currency(batch.sales_total)
|
self.txn_total.value = self.app.render_currency(batch.sales_total)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
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):
|
def add_row_item(self, row):
|
||||||
quantity = self.app.render_quantity(row.quantity)
|
quantity = self.app.render_quantity(row.quantity)
|
||||||
|
@ -282,12 +789,14 @@ class POSView(WuttaView):
|
||||||
self.make_text(f"× {quantity} @ {pretty_price}",
|
self.make_text(f"× {quantity} @ {pretty_price}",
|
||||||
weight=None, italic=True, size=20),
|
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,
|
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||||
),
|
),
|
||||||
border=ft.border.only(bottom=ft.border.BorderSide(1, 'gray')),
|
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):
|
def void_click(self, e):
|
||||||
|
|
||||||
|
@ -303,21 +812,16 @@ class POSView(WuttaView):
|
||||||
# void current batch
|
# void current batch
|
||||||
handler.void_batch(batch, user)
|
handler.void_batch(batch, user)
|
||||||
session.flush()
|
session.flush()
|
||||||
|
self.clear_all()
|
||||||
|
|
||||||
# make new batch
|
# make new batch
|
||||||
batch = handler.get_current_batch(user, create=True)
|
batch = handler.get_current_batch(user, create=True)
|
||||||
|
self.page.shared['txn_display'] = batch.id_str
|
||||||
|
|
||||||
# commit changes
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
# reset txn display
|
self.main_input.focus()
|
||||||
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()
|
self.page.update()
|
||||||
|
|
||||||
def cancel(e):
|
def cancel(e):
|
||||||
|
@ -325,12 +829,30 @@ class POSView(WuttaView):
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
dlg = ft.AlertDialog(
|
dlg = ft.AlertDialog(
|
||||||
modal=True,
|
# modal=True,
|
||||||
title=ft.Text("Confirm VOID"),
|
title=ft.Text("Confirm VOID"),
|
||||||
content=ft.Text("Really VOID this transaction?"),
|
content=ft.Text("Really VOID this transaction?"),
|
||||||
actions=[
|
actions=[
|
||||||
ft.TextButton("Yes", on_click=confirm),
|
ft.Container(content=ft.Text("Yes, VOID",
|
||||||
ft.TextButton("No", on_click=cancel),
|
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
|
self.page.dialog = dlg
|
||||||
|
@ -347,24 +869,39 @@ class POSView(WuttaView):
|
||||||
# tender / execute batch
|
# tender / execute batch
|
||||||
tender = e.control.content.value
|
tender = e.control.content.value
|
||||||
handler.tender_and_execute(batch, user, tender)
|
handler.tender_and_execute(batch, user, tender)
|
||||||
|
self.clear_all()
|
||||||
|
|
||||||
# make new batch
|
# make new batch
|
||||||
batch = handler.get_current_batch(user, create=True)
|
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.commit()
|
||||||
session.close()
|
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.main_input.focus()
|
||||||
self.page.update()
|
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')
|
handler = self.app.get_batch_handler('pos')
|
||||||
session = self.app.make_session()
|
session = self.app.make_session()
|
||||||
model = self.model
|
model = self.model
|
||||||
|
@ -372,10 +909,13 @@ class POSView(WuttaView):
|
||||||
user = session.get(model.User, self.page.shared['user_uuid'])
|
user = session.get(model.User, self.page.shared['user_uuid'])
|
||||||
batch = handler.get_current_batch(user)
|
batch = handler.get_current_batch(user)
|
||||||
|
|
||||||
value = self.main_input.value
|
kwargs = {}
|
||||||
row = handler.process_entry(batch, value)
|
if self.set_quantity.data is not None:
|
||||||
|
kwargs['quantity'] = self.set_quantity.data
|
||||||
|
row = handler.process_entry(batch, value, **kwargs)
|
||||||
if row:
|
if row:
|
||||||
self.add_row_item(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)
|
self.txn_total.value = self.app.render_currency(batch.sales_total)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -391,4 +931,6 @@ class POSView(WuttaView):
|
||||||
|
|
||||||
self.main_input.value = ""
|
self.main_input.value = ""
|
||||||
self.main_input.focus()
|
self.main_input.focus()
|
||||||
|
self.set_quantity.data = None
|
||||||
|
self.set_quantity.value = None
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
Loading…
Reference in a new issue