Add basic mechanism for manager override, use for void txn
This commit is contained in:
parent
21f848cc82
commit
61a06ec5de
|
@ -87,7 +87,7 @@ class WuttaLoginForm(WuttaControl):
|
|||
ft.Row(),
|
||||
ft.Row(),
|
||||
WuttaKeyboard(self.config, on_keypress=self.keyboard_keypress,
|
||||
on_long_backspace=self.long_backspace),
|
||||
on_long_backspace=self.keyboard_long_backspace),
|
||||
]
|
||||
|
||||
return ft.Column(controls=controls,
|
||||
|
@ -245,15 +245,12 @@ class WuttaLoginForm(WuttaControl):
|
|||
else:
|
||||
if self.on_authz_failure:
|
||||
self.on_authz_failure(user, user_display)
|
||||
|
||||
self.clear_login()
|
||||
|
||||
else:
|
||||
if self.on_login_failure:
|
||||
self.on_login_failure(e)
|
||||
|
||||
self.password.focus()
|
||||
self.update()
|
||||
self.clear_login()
|
||||
|
||||
return False
|
||||
|
||||
|
|
|
@ -118,7 +118,6 @@ class WuttaTenkeyMenu(WuttaControl):
|
|||
self,
|
||||
text,
|
||||
font_size=None,
|
||||
bgcolor='green',
|
||||
height=None,
|
||||
width=None,
|
||||
on_click=None,
|
||||
|
@ -133,7 +132,7 @@ class WuttaTenkeyMenu(WuttaControl):
|
|||
if not on_click:
|
||||
on_click = self.tenkey_click
|
||||
|
||||
return self.make_button(text, font_size=font_size, bgcolor=bgcolor,
|
||||
return self.make_button(text, font_size=font_size, bgcolor='green',
|
||||
height=height, width=width,
|
||||
on_click=on_click,
|
||||
on_long_press=on_long_press)
|
||||
|
|
|
@ -89,7 +89,7 @@ class WuttaView(ft.View):
|
|||
|
||||
def show_snackbar(self, text, bgcolor='yellow'):
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black',
|
||||
size=20,
|
||||
size=40,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor=bgcolor,
|
||||
duration=1500)
|
||||
|
|
|
@ -31,6 +31,7 @@ import time
|
|||
import flet as ft
|
||||
|
||||
from .base import WuttaView
|
||||
from wuttapos.controls.loginform import WuttaLoginForm
|
||||
from wuttapos.controls.custlookup import WuttaCustomerLookup
|
||||
from wuttapos.controls.itemlookup import WuttaProductLookup
|
||||
from wuttapos.controls.txnitem import WuttaTxnItem
|
||||
|
@ -98,13 +99,7 @@ class POSView(WuttaView):
|
|||
self.page.session.set('cust_display', handler.get_screen_cust_display(customer=customer))
|
||||
self.informed_refresh()
|
||||
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"CUSTOMER SET: {customer}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='green',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"CUSTOMER SET: {customer}", bgcolor='green')
|
||||
|
||||
def item_click(self, e):
|
||||
|
||||
|
@ -156,13 +151,7 @@ class POSView(WuttaView):
|
|||
if record_badscan:
|
||||
handler.record_badscan(batch, entry, quantity=quantity, user=user)
|
||||
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"PRODUCT NOT FOUND: {entry}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"PRODUCT NOT FOUND: {entry}", bgcolor='yellow')
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
|
@ -228,13 +217,7 @@ class POSView(WuttaView):
|
|||
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',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"CUSTOMER NOT FOUND: {entry}", bgcolor='yellow')
|
||||
self.page.update()
|
||||
return
|
||||
|
||||
|
@ -401,13 +384,7 @@ class POSView(WuttaView):
|
|||
self.informed_refresh()
|
||||
|
||||
dlg.open = False
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("CUSTOMER REMOVED",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar("CUSTOMER REMOVED", bgcolor='yellow')
|
||||
self.reset()
|
||||
|
||||
def cancel(e):
|
||||
|
@ -454,13 +431,7 @@ class POSView(WuttaView):
|
|||
self.reset()
|
||||
|
||||
else: # customer not found
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"CUSTOMER NOT FOUND: {entry}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"CUSTOMER NOT FOUND: {entry}", bgcolor='yellow')
|
||||
# TODO: should use reset() here?
|
||||
self.main_input.focus()
|
||||
self.page.update()
|
||||
|
@ -494,13 +465,7 @@ class POSView(WuttaView):
|
|||
|
||||
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',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"QUANTITY ALREADY SET: {quantity}", bgcolor='yellow')
|
||||
|
||||
else:
|
||||
try:
|
||||
|
@ -516,13 +481,7 @@ class POSView(WuttaView):
|
|||
self.main_input.focus()
|
||||
|
||||
else:
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"INVALID @ QUANTITY: {quantity}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"INVALID @ QUANTITY: {quantity}", bgcolor='yellow')
|
||||
|
||||
self.page.update()
|
||||
|
||||
|
@ -854,12 +813,7 @@ class POSView(WuttaView):
|
|||
feature = e.control.content.value.replace('\n', ' ')
|
||||
if feature:
|
||||
text += f": {feature}"
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(text, bgcolor='yellow')
|
||||
self.page.update()
|
||||
|
||||
def adjust_price_click(self, e):
|
||||
|
@ -1033,65 +987,78 @@ class POSView(WuttaView):
|
|||
self.selected_item.bgcolor = 'blue'
|
||||
self.page.update()
|
||||
|
||||
def void_click(self, e):
|
||||
|
||||
def confirm(e):
|
||||
dlg.open = False
|
||||
def authorized_action(self, perm, action, cancel=None, message=None):
|
||||
auth = self.app.get_auth_handler()
|
||||
|
||||
# current user is assumed if they have the perm
|
||||
session = self.app.make_session()
|
||||
handler = self.get_batch_handler()
|
||||
user = self.get_current_user(session)
|
||||
batch = handler.get_current_batch(user, create=True)
|
||||
|
||||
if self.selected_item:
|
||||
|
||||
# void line
|
||||
row = self.selected_item.data['row']
|
||||
if row.void:
|
||||
# cannot void an already void line
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("LINE ALREADY VOID",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
|
||||
elif row.row_type != self.enum.POS_ROW_TYPE_SELL:
|
||||
# cannot void line unless of type 'sell'
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("LINE DOES NOT ALLOW VOID",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
|
||||
else:
|
||||
# okay, void the line
|
||||
row = session.get(row.__class__, row.uuid)
|
||||
handler.void_row(row, user)
|
||||
self.selected_item.data['row'] = row
|
||||
self.selected_item.content.row = row
|
||||
self.selected_item.content.refresh()
|
||||
self.clear_item_selection()
|
||||
|
||||
# update screen to reflect new balance
|
||||
self.txn_total.value = self.app.render_currency(batch.get_balance())
|
||||
|
||||
else:
|
||||
# void txn
|
||||
handler.void_batch(batch, user)
|
||||
self.clear_all()
|
||||
|
||||
session.commit()
|
||||
has_perm = auth.has_permission(session, user, perm)
|
||||
session.expunge(user)
|
||||
session.close()
|
||||
self.reset()
|
||||
if has_perm:
|
||||
action(user)
|
||||
return
|
||||
|
||||
def cancel(e):
|
||||
# otherwise must prompt for different user credentials...
|
||||
|
||||
def login_cancel(e):
|
||||
dlg.open = False
|
||||
if cancel:
|
||||
cancel()
|
||||
self.reset()
|
||||
|
||||
def login_failure(e):
|
||||
self.show_snackbar("Login failed", bgcolor='yellow')
|
||||
self.page.update()
|
||||
|
||||
def authz_failure(user, user_display):
|
||||
self.show_snackbar(f"User does not have permission: {user_display}",
|
||||
bgcolor='yellow')
|
||||
self.page.update()
|
||||
|
||||
def login_success(user, user_display):
|
||||
dlg.open = False
|
||||
self.page.update()
|
||||
|
||||
# # nb. just in case?
|
||||
# # cf. https://github.com/flet-dev/flet/issues/1670
|
||||
# time.sleep(0.1)
|
||||
|
||||
action(user)
|
||||
self.reset()
|
||||
|
||||
title = "Manager Override"
|
||||
if message:
|
||||
title = f"{title} - {message}"
|
||||
|
||||
dlg = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text(title),
|
||||
content=ft.Container(
|
||||
ft.Column(
|
||||
[
|
||||
ft.Divider(),
|
||||
WuttaLoginForm(self.config,
|
||||
perm_required=perm,
|
||||
on_login_success=login_success,
|
||||
on_login_failure=login_failure,
|
||||
on_authz_failure=authz_failure),
|
||||
],
|
||||
),
|
||||
height=600,
|
||||
),
|
||||
actions=[
|
||||
self.make_button("Cancel", on_click=login_cancel,
|
||||
height=80, width=120),
|
||||
],
|
||||
)
|
||||
|
||||
self.page.dialog = dlg
|
||||
dlg.open = True
|
||||
self.page.update()
|
||||
|
||||
def void_click(self, e):
|
||||
session = self.app.make_session()
|
||||
batch = self.get_current_batch(session, create=False)
|
||||
session.close()
|
||||
|
@ -1102,29 +1069,69 @@ class POSView(WuttaView):
|
|||
self.reset()
|
||||
return
|
||||
|
||||
def confirm(e):
|
||||
dlg.open = False
|
||||
self.page.update()
|
||||
|
||||
if self.selected_item:
|
||||
|
||||
session = self.app.make_session()
|
||||
handler = self.get_batch_handler()
|
||||
user = self.get_current_user(session)
|
||||
|
||||
# void line
|
||||
row = self.selected_item.data['row']
|
||||
if row.void:
|
||||
# cannot void an already void line
|
||||
self.show_snackbar("LINE ALREADY VOID", bgcolor='yellow')
|
||||
|
||||
elif row.row_type != self.enum.POS_ROW_TYPE_SELL:
|
||||
# cannot void line unless of type 'sell'
|
||||
self.show_snackbar("LINE DOES NOT ALLOW VOID", bgcolor='yellow')
|
||||
|
||||
else:
|
||||
# okay, void the line
|
||||
row = session.get(row.__class__, row.uuid)
|
||||
handler.void_row(row, user)
|
||||
session.commit()
|
||||
|
||||
# refresh display
|
||||
self.selected_item.data['row'] = row
|
||||
self.selected_item.content.row = row
|
||||
self.selected_item.content.refresh()
|
||||
self.clear_item_selection()
|
||||
self.txn_total.value = self.app.render_currency(row.batch.get_balance())
|
||||
|
||||
session.close()
|
||||
self.reset()
|
||||
|
||||
else: # void txn
|
||||
|
||||
# nb. do this just in case we must show login dialog
|
||||
# cf. https://github.com/flet-dev/flet/issues/1670
|
||||
time.sleep(0.1)
|
||||
|
||||
self.authorized_action('pos.void_txn', self.void_transaction,
|
||||
message="Void Transaction")
|
||||
|
||||
def cancel(e):
|
||||
dlg.open = False
|
||||
self.reset()
|
||||
|
||||
# prompt to void something
|
||||
target = 'LINE' if self.selected_item else 'TXN'
|
||||
dlg = ft.AlertDialog(
|
||||
title=ft.Text("Confirm VOID"),
|
||||
content=ft.Text("Really VOID this transaction?"),
|
||||
content=ft.Text(f"Really VOID {target}?"),
|
||||
actions=[
|
||||
ft.Container(content=ft.Text("Yes, VOID",
|
||||
size=self.default_font_size,
|
||||
color='black',
|
||||
weight=ft.FontWeight.BOLD),
|
||||
self.make_button(f"VOID {target}", font_size=self.default_font_size,
|
||||
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),
|
||||
self.make_button("Cancel", font_size=self.default_font_size,
|
||||
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),
|
||||
])
|
||||
|
||||
|
@ -1132,6 +1139,19 @@ class POSView(WuttaView):
|
|||
dlg.open = True
|
||||
self.page.update()
|
||||
|
||||
def void_transaction(self, user):
|
||||
session = self.app.make_session()
|
||||
batch = self.get_current_batch(session)
|
||||
user = session.get(user.__class__, user.uuid)
|
||||
handler = self.get_batch_handler()
|
||||
|
||||
handler.void_batch(batch, user)
|
||||
|
||||
session.commit()
|
||||
session.close()
|
||||
self.clear_all()
|
||||
self.reset()
|
||||
|
||||
def tender_click(self, e):
|
||||
session = self.app.make_session()
|
||||
handler = self.get_batch_handler()
|
||||
|
@ -1143,39 +1163,21 @@ class POSView(WuttaView):
|
|||
# nothing to do if no transaction
|
||||
if not batch:
|
||||
session.close()
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("NO TRANSACTION",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar("NO TRANSACTION", bgcolor='yellow')
|
||||
self.reset()
|
||||
return
|
||||
|
||||
# nothing to do if zero sales
|
||||
if not batch.get_balance():
|
||||
session.close()
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("NO SALES",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar("NO SALES", bgcolor='yellow')
|
||||
self.reset()
|
||||
return
|
||||
|
||||
# nothing to do if no amount provided
|
||||
if not self.main_input.value:
|
||||
session.close()
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("MUST SPECIFY AMOUNT",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar("MUST SPECIFY AMOUNT", bgcolor='yellow')
|
||||
self.reset()
|
||||
return
|
||||
|
||||
|
@ -1184,26 +1186,16 @@ class POSView(WuttaView):
|
|||
amount = decimal.Decimal(self.main_input.value)
|
||||
except:
|
||||
session.close()
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"AMOUNT NOT VALID: {self.main_input.value}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"AMOUNT NOT VALID: {self.main_input.value}",
|
||||
bgcolor='yellow')
|
||||
self.reset()
|
||||
return
|
||||
|
||||
# do nothing if @ quantity present
|
||||
if self.set_quantity.data:
|
||||
session.close()
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"QUANTITY NOT ALLOWED FOR TENDER: {self.set_quantity.value}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='yellow',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"QUANTITY NOT ALLOWED FOR TENDER: {self.set_quantity.value}",
|
||||
bgcolor='yellow')
|
||||
self.reset()
|
||||
return
|
||||
|
||||
|
@ -1223,13 +1215,7 @@ class POSView(WuttaView):
|
|||
session.rollback()
|
||||
log.exception("failed to apply tender '%s' for %s in batch %s",
|
||||
code, amount, batch.id_str)
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"ERROR: {error}",
|
||||
color='black',
|
||||
size=20,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
bgcolor='red',
|
||||
duration=1500)
|
||||
self.page.snack_bar.open = True
|
||||
self.show_snackbar(f"ERROR: {error}", bgcolor='red')
|
||||
|
||||
else:
|
||||
session.commit()
|
||||
|
@ -1257,10 +1243,10 @@ class POSView(WuttaView):
|
|||
ft.Container(
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text("Change Back", size=24,
|
||||
ft.Text("Change Due", size=24,
|
||||
weight=ft.FontWeight.BOLD),
|
||||
ft.Divider(),
|
||||
ft.Text("Please give customer change back:",
|
||||
ft.Text("Please give customer their change:",
|
||||
size=20),
|
||||
ft.Text(self.app.render_currency(last_row.tender_total),
|
||||
size=32, weight=ft.FontWeight.BOLD),
|
||||
|
|
Loading…
Reference in a new issue