Move login form to separate control
so we can hopefully use that for "manager override" ..? we'll see
This commit is contained in:
parent
9340e0d1bc
commit
21f848cc82
268
wuttapos/controls/loginform.py
Normal file
268
wuttapos/controls/loginform.py
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
# -*- 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 - login form control
|
||||||
|
"""
|
||||||
|
|
||||||
|
import flet as ft
|
||||||
|
|
||||||
|
from .base import WuttaControl
|
||||||
|
from .keyboard import WuttaKeyboard
|
||||||
|
from .tenkey import WuttaTenkeyMenu
|
||||||
|
|
||||||
|
|
||||||
|
class WuttaLoginForm(WuttaControl):
|
||||||
|
|
||||||
|
def __init__(self, config, *args, **kwargs):
|
||||||
|
|
||||||
|
# permission to be checked for login to succeed
|
||||||
|
self.perm_required = kwargs.pop('perm_required', 'pos.ring_sales')
|
||||||
|
|
||||||
|
# may or may not show the username field
|
||||||
|
# nb. must set this before normal __init__
|
||||||
|
if 'show_username' in kwargs:
|
||||||
|
self.show_username = kwargs.pop('show_username')
|
||||||
|
else:
|
||||||
|
self.show_username = config.getbool('wuttapos', 'login.show_username',
|
||||||
|
default=True)
|
||||||
|
|
||||||
|
# may or may not show 10-key menu instead of full keyboard
|
||||||
|
if 'use_tenkey' in kwargs:
|
||||||
|
self.use_tenkey = kwargs.pop('use_tenkey')
|
||||||
|
else:
|
||||||
|
self.use_tenkey = config.getbool('wuttapos', 'login.use_tenkey',
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
self.on_login_failure = kwargs.pop('on_login_failure', None)
|
||||||
|
self.on_authz_failure = kwargs.pop('on_authz_failure', None)
|
||||||
|
self.on_login_success = kwargs.pop('on_login_success', None)
|
||||||
|
|
||||||
|
super().__init__(config, *args, **kwargs)
|
||||||
|
|
||||||
|
# track which login input has focus
|
||||||
|
self.focused = None
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
|
||||||
|
login_form = self.build_login_form()
|
||||||
|
|
||||||
|
if self.use_tenkey:
|
||||||
|
controls = [
|
||||||
|
ft.Row(
|
||||||
|
[
|
||||||
|
login_form,
|
||||||
|
ft.VerticalDivider(),
|
||||||
|
WuttaTenkeyMenu(self.config, simple=True,
|
||||||
|
on_char=self.tenkey_char,
|
||||||
|
on_enter=self.tenkey_enter),
|
||||||
|
],
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
else: # full keyboard
|
||||||
|
controls = [
|
||||||
|
login_form,
|
||||||
|
ft.Row(),
|
||||||
|
ft.Row(),
|
||||||
|
ft.Row(),
|
||||||
|
WuttaKeyboard(self.config, on_keypress=self.keyboard_keypress,
|
||||||
|
on_long_backspace=self.long_backspace),
|
||||||
|
]
|
||||||
|
|
||||||
|
return ft.Column(controls=controls,
|
||||||
|
expand=True,
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER)
|
||||||
|
|
||||||
|
def build_login_form(self):
|
||||||
|
form_fields = []
|
||||||
|
|
||||||
|
self.password = ft.TextField(label="Password", width=200, password=True,
|
||||||
|
on_submit=self.password_submit,
|
||||||
|
on_focus=self.password_focus,
|
||||||
|
autofocus=not self.show_username)
|
||||||
|
self.focused = self.password
|
||||||
|
|
||||||
|
if self.show_username:
|
||||||
|
self.username = ft.TextField(label="Login", width=200,
|
||||||
|
on_submit=self.username_submit,
|
||||||
|
on_focus=self.username_focus,
|
||||||
|
autofocus=True)
|
||||||
|
form_fields.append(self.username)
|
||||||
|
self.focused = self.username
|
||||||
|
|
||||||
|
form_fields.append(self.password)
|
||||||
|
|
||||||
|
login_button = self.make_button("Login",
|
||||||
|
height=60,
|
||||||
|
width=60 * 2.5,
|
||||||
|
bgcolor='blue',
|
||||||
|
on_click=self.attempt_login)
|
||||||
|
|
||||||
|
reset_button = self.make_button("Clear",
|
||||||
|
height=60,
|
||||||
|
width=60 * 2.5,
|
||||||
|
on_click=self.clear_login)
|
||||||
|
|
||||||
|
if self.use_tenkey:
|
||||||
|
form_fields.extend([
|
||||||
|
ft.Row(),
|
||||||
|
ft.Row(),
|
||||||
|
ft.Row(
|
||||||
|
[
|
||||||
|
reset_button,
|
||||||
|
login_button,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
return ft.Column(
|
||||||
|
controls=form_fields,
|
||||||
|
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||||
|
)
|
||||||
|
|
||||||
|
else: # full keyboard
|
||||||
|
form_fields.extend([
|
||||||
|
login_button,
|
||||||
|
reset_button,
|
||||||
|
])
|
||||||
|
return ft.Row(
|
||||||
|
controls=form_fields,
|
||||||
|
alignment=ft.MainAxisAlignment.CENTER,
|
||||||
|
)
|
||||||
|
|
||||||
|
def keyboard_keypress(self, key):
|
||||||
|
assert self.focused
|
||||||
|
|
||||||
|
if key == '⏎': # ENTER
|
||||||
|
|
||||||
|
# attempt to submit the login form..
|
||||||
|
if self.show_username and self.focused is self.username:
|
||||||
|
self.username_submit()
|
||||||
|
else:
|
||||||
|
if self.password_submit():
|
||||||
|
|
||||||
|
# here the login has totally worked, which means
|
||||||
|
# this form has fulfilled its purpose. hence must
|
||||||
|
# exit early to avoid update() in case we are to
|
||||||
|
# be redirected etc. otherwise may get errors
|
||||||
|
# trying to update controls which have already
|
||||||
|
# been dropped from the page..
|
||||||
|
return
|
||||||
|
|
||||||
|
elif key == '⌫':
|
||||||
|
self.focused.value = self.focused.value[:-1]
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.focused.value += key
|
||||||
|
|
||||||
|
self.focused.focus()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def keyboard_long_backspace(self):
|
||||||
|
assert self.focused
|
||||||
|
self.focused.value = ''
|
||||||
|
self.focused.focus()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def tenkey_char(self, key):
|
||||||
|
if key == '@':
|
||||||
|
return
|
||||||
|
|
||||||
|
self.focused.value = f"{self.focused.value or ''}{key}"
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def tenkey_enter(self, e):
|
||||||
|
if self.show_username and self.focused is self.username:
|
||||||
|
self.username_submit(e)
|
||||||
|
self.update()
|
||||||
|
else:
|
||||||
|
if not self.password_submit(e):
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def username_focus(self, e):
|
||||||
|
self.focused = self.username
|
||||||
|
|
||||||
|
def username_submit(self, e=None):
|
||||||
|
if self.username.value:
|
||||||
|
self.password.focus()
|
||||||
|
else:
|
||||||
|
self.username.focus()
|
||||||
|
|
||||||
|
def password_focus(self, e):
|
||||||
|
self.focused = self.password
|
||||||
|
|
||||||
|
def password_submit(self, e=None):
|
||||||
|
if self.password.value:
|
||||||
|
return self.attempt_login(e)
|
||||||
|
else:
|
||||||
|
self.password.focus()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def attempt_login(self, e=None):
|
||||||
|
if self.show_username and not self.username.value:
|
||||||
|
self.username.focus()
|
||||||
|
return False
|
||||||
|
if not self.password.value:
|
||||||
|
self.password.focus()
|
||||||
|
return False
|
||||||
|
|
||||||
|
session = self.app.make_session()
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
user = auth.authenticate_user(session,
|
||||||
|
self.username.value if self.show_username else None,
|
||||||
|
self.password.value)
|
||||||
|
user_display = str(user) if user else None
|
||||||
|
has_perm = auth.has_permission(session, user, self.perm_required) if user else False
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
|
||||||
|
if has_perm:
|
||||||
|
if self.on_login_success:
|
||||||
|
self.on_login_success(user, user_display)
|
||||||
|
return True
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_login(self, e=None):
|
||||||
|
if self.show_username:
|
||||||
|
self.username.value = ""
|
||||||
|
self.password.value = ""
|
||||||
|
if self.show_username:
|
||||||
|
self.username.focus()
|
||||||
|
else:
|
||||||
|
self.password.focus()
|
||||||
|
self.update()
|
|
@ -87,15 +87,13 @@ class WuttaView(ft.View):
|
||||||
kwargs.setdefault('height', 100)
|
kwargs.setdefault('height', 100)
|
||||||
return ft.Image(src=logo, **kwargs)
|
return ft.Image(src=logo, **kwargs)
|
||||||
|
|
||||||
def show_snackbar(self, text, bgcolor='yellow', update=True):
|
def show_snackbar(self, text, bgcolor='yellow'):
|
||||||
self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black',
|
self.page.snack_bar = ft.SnackBar(ft.Text(text, color='black',
|
||||||
size=20,
|
size=20,
|
||||||
weight=ft.FontWeight.BOLD),
|
weight=ft.FontWeight.BOLD),
|
||||||
bgcolor=bgcolor,
|
bgcolor=bgcolor,
|
||||||
duration=1500)
|
duration=1500)
|
||||||
self.page.snack_bar.open = True
|
self.page.snack_bar.open = True
|
||||||
if update:
|
|
||||||
self.page.update()
|
|
||||||
|
|
||||||
|
|
||||||
class WuttaViewContainer(ft.Container):
|
class WuttaViewContainer(ft.Container):
|
||||||
|
|
|
@ -27,8 +27,7 @@ WuttaPOS - login view
|
||||||
import flet as ft
|
import flet as ft
|
||||||
|
|
||||||
from .base import WuttaView
|
from .base import WuttaView
|
||||||
from wuttapos.controls.keyboard import WuttaKeyboard
|
from wuttapos.controls.loginform import WuttaLoginForm
|
||||||
from wuttapos.controls.tenkey import WuttaTenkeyMenu
|
|
||||||
|
|
||||||
|
|
||||||
class LoginView(WuttaView):
|
class LoginView(WuttaView):
|
||||||
|
@ -36,31 +35,9 @@ class LoginView(WuttaView):
|
||||||
Main POS view for WuttaPOS
|
Main POS view for WuttaPOS
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config, *args, **kwargs):
|
|
||||||
|
|
||||||
# may or may not show the username field
|
|
||||||
# nb. must set this before normal __init__
|
|
||||||
if 'show_username' in kwargs:
|
|
||||||
self.show_username = kwargs.pop('show_username')
|
|
||||||
else:
|
|
||||||
self.show_username = config.getbool('wuttapos', 'login.show_username',
|
|
||||||
default=True)
|
|
||||||
|
|
||||||
# may or may not show 10-key menu instead of full keyboard
|
|
||||||
if 'use_tenkey' in kwargs:
|
|
||||||
self.use_tenkey = kwargs.pop('use_tenkey')
|
|
||||||
else:
|
|
||||||
self.use_tenkey = config.getbool('wuttapos', 'login.use_tenkey',
|
|
||||||
default=False)
|
|
||||||
|
|
||||||
# build controls
|
|
||||||
super().__init__(config, *args, **kwargs)
|
|
||||||
|
|
||||||
# track which login input has focus
|
|
||||||
self.focused = None
|
|
||||||
|
|
||||||
def build_controls(self):
|
def build_controls(self):
|
||||||
title = self.app.get_title()
|
title = self.app.get_title()
|
||||||
|
|
||||||
controls = [
|
controls = [
|
||||||
ft.Row(
|
ft.Row(
|
||||||
[self.make_logo_image(height=200)],
|
[self.make_logo_image(height=200)],
|
||||||
|
@ -73,33 +50,12 @@ class LoginView(WuttaView):
|
||||||
ft.Row(),
|
ft.Row(),
|
||||||
ft.Row(),
|
ft.Row(),
|
||||||
ft.Row(),
|
ft.Row(),
|
||||||
|
WuttaLoginForm(self.config,
|
||||||
|
on_login_failure=self.login_failure,
|
||||||
|
on_authz_failure=self.authz_failure,
|
||||||
|
on_login_success=self.login_success),
|
||||||
]
|
]
|
||||||
|
|
||||||
login_form = self.build_login_form()
|
|
||||||
|
|
||||||
if self.use_tenkey:
|
|
||||||
controls.extend([
|
|
||||||
ft.Row(
|
|
||||||
[
|
|
||||||
login_form,
|
|
||||||
ft.VerticalDivider(),
|
|
||||||
WuttaTenkeyMenu(self.config, simple=True,
|
|
||||||
on_char=self.tenkey_char,
|
|
||||||
on_enter=self.tenkey_enter),
|
|
||||||
],
|
|
||||||
alignment=ft.MainAxisAlignment.CENTER,
|
|
||||||
),
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
controls.extend([
|
|
||||||
login_form,
|
|
||||||
ft.Row(),
|
|
||||||
ft.Row(),
|
|
||||||
ft.Row(),
|
|
||||||
WuttaKeyboard(self.config, on_keypress=self.keypress,
|
|
||||||
on_long_backspace=self.long_backspace),
|
|
||||||
])
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
self.build_header(),
|
self.build_header(),
|
||||||
ft.Column(controls=controls,
|
ft.Column(controls=controls,
|
||||||
|
@ -107,179 +63,21 @@ class LoginView(WuttaView):
|
||||||
alignment=ft.MainAxisAlignment.CENTER),
|
alignment=ft.MainAxisAlignment.CENTER),
|
||||||
]
|
]
|
||||||
|
|
||||||
def build_login_form(self):
|
def login_failure(self, e):
|
||||||
form_fields = []
|
self.show_snackbar("Login failed!", bgcolor='yellow')
|
||||||
|
|
||||||
self.password = ft.TextField(label="Password", width=200, password=True,
|
|
||||||
on_submit=self.password_submit,
|
|
||||||
on_focus=self.password_focus,
|
|
||||||
autofocus=not self.show_username)
|
|
||||||
self.focused = self.password
|
|
||||||
|
|
||||||
if self.show_username:
|
|
||||||
self.username = ft.TextField(label="Login", width=200,
|
|
||||||
on_submit=self.username_submit,
|
|
||||||
on_focus=self.username_focus,
|
|
||||||
autofocus=True)
|
|
||||||
form_fields.append(self.username)
|
|
||||||
self.focused = self.username
|
|
||||||
|
|
||||||
form_fields.append(self.password)
|
|
||||||
|
|
||||||
login_button = ft.Container(
|
|
||||||
content=ft.Text("Login", size=20,
|
|
||||||
weight=ft.FontWeight.BOLD),
|
|
||||||
height=60,
|
|
||||||
width=60 * 2.5,
|
|
||||||
alignment=ft.alignment.center,
|
|
||||||
border=ft.border.all(1, 'black'),
|
|
||||||
border_radius=ft.border_radius.all(5),
|
|
||||||
bgcolor='blue',
|
|
||||||
on_click=self.attempt_login)
|
|
||||||
|
|
||||||
reset_button = ft.Container(
|
|
||||||
content=ft.Text("Clear", size=20,
|
|
||||||
weight=ft.FontWeight.BOLD),
|
|
||||||
height=60,
|
|
||||||
width=60 * 2.5,
|
|
||||||
alignment=ft.alignment.center,
|
|
||||||
border=ft.border.all(1, 'black'),
|
|
||||||
border_radius=ft.border_radius.all(5),
|
|
||||||
on_click=self.clear_login)
|
|
||||||
|
|
||||||
if self.use_tenkey:
|
|
||||||
form_fields.extend([
|
|
||||||
ft.Row(),
|
|
||||||
ft.Row(),
|
|
||||||
ft.Row(
|
|
||||||
[
|
|
||||||
reset_button,
|
|
||||||
login_button,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
form_fields.extend([
|
|
||||||
login_button,
|
|
||||||
reset_button,
|
|
||||||
])
|
|
||||||
|
|
||||||
if self.use_tenkey:
|
|
||||||
return ft.Column(
|
|
||||||
controls=form_fields,
|
|
||||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return ft.Row(
|
|
||||||
controls=form_fields,
|
|
||||||
alignment=ft.MainAxisAlignment.CENTER,
|
|
||||||
)
|
|
||||||
|
|
||||||
def keypress(self, key):
|
|
||||||
assert self.focused
|
|
||||||
if key == '⏎':
|
|
||||||
if self.show_username and self.focused is self.username:
|
|
||||||
self.username_submit()
|
|
||||||
else:
|
|
||||||
# nb. when this succeeds, we will have been redirected
|
|
||||||
# to /pos so must exit here to avoid update() etc.
|
|
||||||
if self.password_submit():
|
|
||||||
return
|
|
||||||
elif key == '⌫':
|
|
||||||
self.focused.value = self.focused.value[:-1]
|
|
||||||
else:
|
|
||||||
self.focused.value += key
|
|
||||||
|
|
||||||
self.focused.focus()
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def long_backspace(self):
|
|
||||||
assert self.focused
|
|
||||||
self.focused.value = ''
|
|
||||||
self.focused.focus()
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def tenkey_char(self, key):
|
|
||||||
if key == '@':
|
|
||||||
return
|
|
||||||
|
|
||||||
self.focused.value = f"{self.focused.value or ''}{key}"
|
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
def tenkey_enter(self, e):
|
def authz_failure(self, user, user_display):
|
||||||
if self.show_username and self.focused is self.username:
|
self.show_snackbar(f"User not allowed to ring sales: {user_display}",
|
||||||
self.username_submit()
|
bgcolor='yellow')
|
||||||
self.update()
|
|
||||||
else:
|
|
||||||
if not self.password_submit():
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def username_focus(self, e):
|
|
||||||
self.focused = self.username
|
|
||||||
|
|
||||||
def username_submit(self, e=None):
|
|
||||||
if self.username.value:
|
|
||||||
self.password.focus()
|
|
||||||
else:
|
|
||||||
self.username.focus()
|
|
||||||
|
|
||||||
def password_focus(self, e):
|
|
||||||
self.focused = self.password
|
|
||||||
|
|
||||||
def password_submit(self, e=None):
|
|
||||||
if self.password.value:
|
|
||||||
return self.attempt_login()
|
|
||||||
else:
|
|
||||||
self.password.focus()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clear_login(self, e):
|
|
||||||
if self.show_username:
|
|
||||||
self.username.value = ""
|
|
||||||
self.password.value = ""
|
|
||||||
if self.show_username:
|
|
||||||
self.username.focus()
|
|
||||||
else:
|
|
||||||
self.password.focus()
|
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
def attempt_login(self, e=None):
|
def login_success(self, user, user_display):
|
||||||
if self.show_username and not self.username.value:
|
self.page.session.set('user_uuid', user.uuid)
|
||||||
self.username.focus()
|
self.page.session.set('user_display', user_display)
|
||||||
return False
|
|
||||||
if not self.password.value:
|
|
||||||
self.password.focus()
|
|
||||||
return False
|
|
||||||
|
|
||||||
session = self.app.make_session()
|
# TODO: hacky but works for now
|
||||||
auth = self.app.get_auth_handler()
|
if not self.config.production():
|
||||||
user = auth.authenticate_user(session,
|
self.page.client_storage.set('user_uuid', user.uuid)
|
||||||
self.username.value if self.show_username else None,
|
|
||||||
self.password.value)
|
|
||||||
user_display = str(user) if user else None
|
|
||||||
session.close()
|
|
||||||
|
|
||||||
if user:
|
self.page.go('/pos')
|
||||||
# handle success
|
|
||||||
self.page.session.set('user_uuid', user.uuid)
|
|
||||||
self.page.session.set('user_display', user_display)
|
|
||||||
|
|
||||||
# TODO: hacky but works for now
|
|
||||||
if not self.config.production():
|
|
||||||
self.page.client_storage.set('user_uuid', user.uuid)
|
|
||||||
|
|
||||||
self.page.go('/pos')
|
|
||||||
return True
|
|
||||||
|
|
||||||
else:
|
|
||||||
# handle failure
|
|
||||||
self.page.snack_bar = ft.SnackBar(ft.Text("Login failed!",
|
|
||||||
color='black',
|
|
||||||
weight=ft.FontWeight.BOLD),
|
|
||||||
bgcolor='yellow',
|
|
||||||
duration=1500)
|
|
||||||
self.page.snack_bar.open = True
|
|
||||||
self.password.focus()
|
|
||||||
self.page.update()
|
|
||||||
return False
|
|
||||||
|
|
|
@ -142,7 +142,8 @@ class POSView(WuttaView):
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
if row.row_type == self.enum.POS_ROW_TYPE_BADPRICE:
|
if row.row_type == self.enum.POS_ROW_TYPE_BADPRICE:
|
||||||
self.show_snackbar(f"Product has invalid price: {row.item_entry}")
|
self.show_snackbar(f"Product has invalid price: {row.item_entry}",
|
||||||
|
bgcolor='yellow')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.add_row_item(row)
|
self.add_row_item(row)
|
||||||
|
@ -864,12 +865,12 @@ class POSView(WuttaView):
|
||||||
def adjust_price_click(self, e):
|
def adjust_price_click(self, e):
|
||||||
|
|
||||||
if not len(self.items.controls):
|
if not len(self.items.controls):
|
||||||
self.show_snackbar("There are no line items", update=False)
|
self.show_snackbar("There are no line items", bgcolor='yellow')
|
||||||
self.reset()
|
self.reset()
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.selected_item:
|
if not self.selected_item:
|
||||||
self.show_snackbar("Must first select a line item", update=False)
|
self.show_snackbar("Must first select a line item", bgcolor='yellow')
|
||||||
self.main_input.focus()
|
self.main_input.focus()
|
||||||
self.page.update()
|
self.page.update()
|
||||||
return
|
return
|
||||||
|
@ -894,7 +895,7 @@ class POSView(WuttaView):
|
||||||
try:
|
try:
|
||||||
price = decimal.Decimal(price_override.value)
|
price = decimal.Decimal(price_override.value)
|
||||||
except decimal.InvalidOperation:
|
except decimal.InvalidOperation:
|
||||||
self.show_snackbar(f"Price is not valid: {price_override.value}", update=False)
|
self.show_snackbar(f"Price is not valid: {price_override.value}", bgcolor='yellow')
|
||||||
self.main_input.focus()
|
self.main_input.focus()
|
||||||
self.page.update()
|
self.page.update()
|
||||||
return
|
return
|
||||||
|
@ -1095,52 +1096,37 @@ class POSView(WuttaView):
|
||||||
batch = self.get_current_batch(session, create=False)
|
batch = self.get_current_batch(session, create=False)
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
if batch:
|
# nothing to void if no batch
|
||||||
|
if not batch:
|
||||||
|
self.show_snackbar("No transaction", bgcolor='yellow')
|
||||||
|
self.reset()
|
||||||
|
return
|
||||||
|
|
||||||
# prompt to void something
|
dlg = ft.AlertDialog(
|
||||||
target = 'LINE' if self.selected_item else 'TXN'
|
title=ft.Text("Confirm VOID"),
|
||||||
dlg = ft.AlertDialog(
|
content=ft.Text("Really VOID this transaction?"),
|
||||||
title=ft.Text("Confirm VOID"),
|
actions=[
|
||||||
content=ft.Text(f"Really VOID {target}?"),
|
ft.Container(content=ft.Text("Yes, VOID",
|
||||||
actions=[
|
size=self.default_font_size,
|
||||||
ft.Container(content=ft.Text(f"VOID {target}",
|
color='black',
|
||||||
size=self.default_font_size,
|
weight=ft.FontWeight.BOLD),
|
||||||
color='black',
|
height=self.default_button_size,
|
||||||
weight=ft.FontWeight.BOLD),
|
width=self.default_button_size * 2.5,
|
||||||
height=self.default_button_size,
|
alignment=ft.alignment.center,
|
||||||
width=self.default_button_size * 2.5,
|
bgcolor='red',
|
||||||
alignment=ft.alignment.center,
|
border=ft.border.all(1, 'black'),
|
||||||
bgcolor='red',
|
border_radius=ft.border_radius.all(5),
|
||||||
border=ft.border.all(1, 'black'),
|
on_click=confirm),
|
||||||
border_radius=ft.border_radius.all(5),
|
ft.Container(content=ft.Text("Cancel",
|
||||||
on_click=confirm),
|
size=self.default_font_size,
|
||||||
ft.Container(content=ft.Text("Cancel",
|
weight=ft.FontWeight.BOLD),
|
||||||
size=self.default_font_size,
|
height=self.default_button_size,
|
||||||
weight=ft.FontWeight.BOLD),
|
width=self.default_button_size * 2.5,
|
||||||
height=self.default_button_size,
|
alignment=ft.alignment.center,
|
||||||
width=self.default_button_size * 2.5,
|
border=ft.border.all(1, 'black'),
|
||||||
alignment=ft.alignment.center,
|
border_radius=ft.border_radius.all(5),
|
||||||
border=ft.border.all(1, 'black'),
|
on_click=cancel),
|
||||||
border_radius=ft.border_radius.all(5),
|
])
|
||||||
on_click=cancel),
|
|
||||||
])
|
|
||||||
|
|
||||||
else: # nothing to void
|
|
||||||
|
|
||||||
dlg = ft.AlertDialog(
|
|
||||||
title=ft.Text("No Transaction"),
|
|
||||||
content=ft.Text("You do not have a transaction open currently.", size=20),
|
|
||||||
actions=[
|
|
||||||
ft.Container(content=ft.Text("Close",
|
|
||||||
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
|
||||||
dlg.open = True
|
dlg.open = True
|
||||||
|
|
Loading…
Reference in a new issue