Add basic mechanism for manager override, use for void txn

This commit is contained in:
Lance Edgar 2023-10-06 09:18:07 -05:00
parent 21f848cc82
commit 61a06ec5de
4 changed files with 168 additions and 186 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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 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()
user = self.get_current_user(session)
has_perm = auth.has_permission(session, user, perm)
session.expunge(user)
session.close()
if has_perm:
action(user)
return
# 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):
def confirm(e):
dlg.open = False
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()
session.close()
self.reset()
def cancel(e):
dlg.open = False
self.reset()
session = self.app.make_session()
batch = self.get_current_batch(session, create=False)
session.close()
@ -1102,36 +1069,89 @@ 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),
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.make_button(f"VOID {target}", font_size=self.default_font_size,
height=self.default_button_size,
width=self.default_button_size * 2.5,
bgcolor='red',
on_click=confirm),
self.make_button("Cancel", font_size=self.default_font_size,
height=self.default_button_size,
width=self.default_button_size * 2.5,
on_click=cancel),
])
self.page.dialog = dlg
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),