Move customer lookup into a control
This commit is contained in:
parent
943f723ae6
commit
1fd78d0c2f
|
@ -118,13 +118,22 @@ def main(page: ft.Page):
|
|||
if user:
|
||||
page.session.set('user_uuid', uuid)
|
||||
page.session.set('user_display', str(user))
|
||||
handler = get_pos_batch_handler(config)
|
||||
batch = handler.get_current_batch(user, create=False)
|
||||
if batch:
|
||||
page.session.set('txn_display', batch.id_str)
|
||||
if batch.customer:
|
||||
page.session.set('cust_uuid', batch.customer.uuid)
|
||||
key = app.get_customer_key_field()
|
||||
value = getattr(batch.customer, key)
|
||||
page.session.set('cust_display', str(value or ''))
|
||||
session.close()
|
||||
page.go('/pos')
|
||||
return
|
||||
|
||||
session.close()
|
||||
if user:
|
||||
page.go('/pos')
|
||||
else:
|
||||
page.go('/login')
|
||||
else:
|
||||
page.go('/login')
|
||||
|
||||
page.go('/login')
|
||||
|
||||
|
||||
# TODO: can we inject config to the main() via ft.app() kwargs somehow?
|
||||
|
|
|
@ -33,3 +33,6 @@ class WuttaControl(ft.UserControl):
|
|||
super().__init__(*args, **kwargs)
|
||||
self.config = config
|
||||
self.app = config.get_app()
|
||||
|
||||
def informed_refresh(self, **kwargs):
|
||||
pass
|
||||
|
|
236
wuttapos/controls/custlookup.py
Normal file
236
wuttapos/controls/custlookup.py
Normal file
|
@ -0,0 +1,236 @@
|
|||
# -*- 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 - customer lookup control
|
||||
"""
|
||||
|
||||
import flet as ft
|
||||
|
||||
from .base import WuttaControl
|
||||
from .keyboard import WuttaKeyboard
|
||||
|
||||
|
||||
class WuttaCustomerLookup(WuttaControl):
|
||||
|
||||
default_font_size = 40
|
||||
font_size = default_font_size * 0.8
|
||||
default_button_height_dlg = 80
|
||||
disabled_bgcolor = '#aaaaaa'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.initial_search = kwargs.pop('initial_search', None)
|
||||
self.on_customer = kwargs.pop('on_customer', None)
|
||||
self.on_cancel = kwargs.pop('on_cancel', None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# track current selection
|
||||
self.selected_customer_uuid = None
|
||||
self.selected_control = None
|
||||
|
||||
def build(self):
|
||||
|
||||
self.searchbox = ft.TextField("", text_size=self.font_size * 0.8,
|
||||
on_submit=self.lookup,
|
||||
autofocus=True,
|
||||
expand=True)
|
||||
|
||||
self.search_results = ft.DataTable(
|
||||
columns=[
|
||||
ft.DataColumn(self.make_cell_text(self.app.get_customer_key_label())),
|
||||
ft.DataColumn(self.make_cell_text("Name")),
|
||||
ft.DataColumn(self.make_cell_text("Phone")),
|
||||
ft.DataColumn(self.make_cell_text("Email")),
|
||||
],
|
||||
)
|
||||
|
||||
self.no_results = ft.Text("NO RESULTS", size=32, color='red',
|
||||
weight=ft.FontWeight.BOLD,
|
||||
visible=False)
|
||||
|
||||
self.select_button = ft.Container(
|
||||
content=ft.Text("Select", size=self.font_size * 0.8),
|
||||
alignment=ft.alignment.center,
|
||||
height=self.default_button_height_dlg * 0.8,
|
||||
width=self.default_button_height_dlg * 1.3,
|
||||
border=ft.border.all(1, 'black'),
|
||||
border_radius=ft.border_radius.all(5),
|
||||
on_click=self.select_customer,
|
||||
disabled=True,
|
||||
bgcolor=self.disabled_bgcolor,
|
||||
)
|
||||
|
||||
return ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("SEARCH FOR:"),
|
||||
self.searchbox,
|
||||
ft.Container(
|
||||
content=ft.Text("Lookup", size=self.font_size * 0.8),
|
||||
alignment=ft.alignment.center,
|
||||
height=self.default_button_height_dlg * 0.8,
|
||||
width=self.default_button_height_dlg * 1.3,
|
||||
border=ft.border.all(1, 'black'),
|
||||
border_radius=ft.border_radius.all(5),
|
||||
on_click=self.lookup,
|
||||
bgcolor='blue',
|
||||
),
|
||||
ft.Container(
|
||||
content=ft.Text("Reset", size=self.font_size * 0.8),
|
||||
alignment=ft.alignment.center,
|
||||
height=self.default_button_height_dlg * 0.8,
|
||||
width=self.default_button_height_dlg * 1.3,
|
||||
border=ft.border.all(1, 'black'),
|
||||
border_radius=ft.border_radius.all(5),
|
||||
on_click=self.reset,
|
||||
bgcolor='yellow',
|
||||
),
|
||||
ft.Container(
|
||||
content=ft.Text("Cancel", size=self.font_size * 0.8),
|
||||
alignment=ft.alignment.center,
|
||||
height=self.default_button_height_dlg * 0.8,
|
||||
width=self.default_button_height_dlg * 1.3,
|
||||
border=ft.border.all(1, 'black'),
|
||||
border_radius=ft.border_radius.all(5),
|
||||
on_click=self.cancel,
|
||||
),
|
||||
],
|
||||
),
|
||||
ft.Divider(),
|
||||
WuttaKeyboard(self.config, on_keypress=self.keypress),
|
||||
ft.Divider(),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
self.search_results,
|
||||
self.no_results,
|
||||
],
|
||||
expand=True,
|
||||
),
|
||||
self.select_button,
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def did_mount(self):
|
||||
if self.initial_search:
|
||||
self.searchbox.value = self.initial_search
|
||||
self.initial_search = None # only do it once
|
||||
self.update()
|
||||
self.lookup()
|
||||
|
||||
def make_cell_text(self, text):
|
||||
return ft.Text(text, size=32)
|
||||
|
||||
def make_cell(self, text):
|
||||
return ft.DataCell(self.make_cell_text(text))
|
||||
|
||||
def cancel(self, e):
|
||||
if self.on_cancel:
|
||||
self.on_cancel(e)
|
||||
|
||||
def keypress(self, key):
|
||||
if key == '⏎':
|
||||
self.lookup()
|
||||
else:
|
||||
if key == '⌫':
|
||||
self.searchbox.value = self.searchbox.value[:-1]
|
||||
else:
|
||||
self.searchbox.value += key
|
||||
self.searchbox.focus()
|
||||
self.update()
|
||||
|
||||
def lookup(self, e=None):
|
||||
entry = self.searchbox.value
|
||||
if not entry:
|
||||
self.searchbox.focus()
|
||||
self.update()
|
||||
return
|
||||
|
||||
session = self.app.make_session()
|
||||
results = self.app.get_clientele_handler().search_customers(session, entry)
|
||||
|
||||
self.search_results.rows.clear()
|
||||
self.selected_customer_uuid = None
|
||||
self.select_button.disabled = True
|
||||
self.select_button.bgcolor = self.disabled_bgcolor
|
||||
|
||||
if results:
|
||||
for customer in results:
|
||||
self.search_results.rows.append(ft.DataRow(
|
||||
cells=[
|
||||
self.make_cell(customer['_customer_key_']),
|
||||
self.make_cell(customer['name']),
|
||||
self.make_cell(customer['phone_number']),
|
||||
self.make_cell(customer['email_address']),
|
||||
],
|
||||
on_select_changed=self.select_changed,
|
||||
data={'uuid': customer['uuid']},
|
||||
))
|
||||
self.no_results.visible = False
|
||||
|
||||
else:
|
||||
self.no_results.value = f"NO RESULTS FOR: {entry}"
|
||||
self.no_results.visible = True
|
||||
|
||||
self.searchbox.focus()
|
||||
self.update()
|
||||
|
||||
def reset(self, e):
|
||||
self.searchbox.value = ""
|
||||
self.search_results.rows.clear()
|
||||
self.no_results.visible = False
|
||||
self.selected_customer_uuid = None
|
||||
self.select_button.disabled = True
|
||||
self.select_button.bgcolor = self.disabled_bgcolor
|
||||
self.searchbox.focus()
|
||||
self.update()
|
||||
|
||||
def select_changed(self, e):
|
||||
|
||||
if e.data: # selected
|
||||
if self.selected_control:
|
||||
self.selected_control.color = None
|
||||
self.selected_customer_uuid = e.control.data['uuid']
|
||||
self.selected_control = e.control
|
||||
self.selected_control.color = ft.colors.BLUE
|
||||
self.select_button.disabled = False
|
||||
self.select_button.bgcolor = 'blue'
|
||||
else:
|
||||
if self.selected_control:
|
||||
self.selected_control.color = None
|
||||
self.selected_control = None
|
||||
self.selected_customer_uuid = None
|
||||
self.select_button.disabled = True
|
||||
self.select_button.bgcolor = self.disabled_bgcolor
|
||||
e.control.color = None
|
||||
|
||||
self.update()
|
||||
|
||||
def select_customer(self, e):
|
||||
if not self.selected_customer_uuid:
|
||||
raise RuntimeError("no customer selected?")
|
||||
if self.on_customer:
|
||||
self.on_customer(self.selected_customer_uuid)
|
|
@ -56,6 +56,9 @@ class WuttaHeader(WuttaControl):
|
|||
return ft.Row(controls)
|
||||
|
||||
def did_mount(self):
|
||||
self.informed_refresh()
|
||||
|
||||
def informed_refresh(self):
|
||||
self.update_txn_display()
|
||||
self.update_cust_display()
|
||||
self.update_user_display()
|
||||
|
@ -84,13 +87,10 @@ class WuttaHeader(WuttaControl):
|
|||
self.logout_divider.visible = True
|
||||
|
||||
def logout_click(self, e):
|
||||
|
||||
# TODO: hacky but works for now
|
||||
if not self.config.production():
|
||||
self.page.client_storage.set('user_uuid', '')
|
||||
|
||||
self.page.session.set('user_uuid', None)
|
||||
self.page.session.set('user_display', None)
|
||||
self.page.session.set('txn_display', None)
|
||||
self.page.session.set('cust_display', None)
|
||||
|
||||
self.page.session.clear()
|
||||
self.page.go('/login')
|
||||
|
|
|
@ -31,7 +31,7 @@ import time
|
|||
import flet as ft
|
||||
|
||||
from .base import WuttaView
|
||||
from wuttapos.controls.keyboard import WuttaKeyboard
|
||||
from wuttapos.controls.custlookup import WuttaCustomerLookup
|
||||
from wuttapos.util import get_pos_batch_handler
|
||||
|
||||
|
||||
|
@ -49,6 +49,20 @@ class POSView(WuttaView):
|
|||
|
||||
disabled_bgcolor = '#aaaaaa'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# keep a list of "informed" controls - i.e. child controls
|
||||
# within this view, which need to stay abreast of global
|
||||
# changes to the transaction, customer etc.
|
||||
self.informed_controls = []
|
||||
if hasattr(self, 'header'):
|
||||
self.informed_controls.append(self.header)
|
||||
|
||||
def informed_refresh(self):
|
||||
for control in self.informed_controls:
|
||||
control.informed_refresh()
|
||||
|
||||
def get_batch_handler(self):
|
||||
return get_pos_batch_handler(self.config)
|
||||
|
||||
|
@ -60,14 +74,11 @@ class POSView(WuttaView):
|
|||
handler = self.get_batch_handler()
|
||||
handler.set_customer(batch, customer)
|
||||
|
||||
key = self.app.get_customer_key_field()
|
||||
value = getattr(customer, key)
|
||||
key_field = self.app.get_customer_key_field()
|
||||
customer_key = getattr(customer, key_field)
|
||||
self.page.session.set('cust_uuid', customer.uuid)
|
||||
self.page.session.set('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.session.set('cust_display', str(customer_key or ''))
|
||||
self.informed_refresh()
|
||||
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text(f"CUSTOMER SET: {customer}",
|
||||
color='black',
|
||||
|
@ -76,100 +87,11 @@ class POSView(WuttaView):
|
|||
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
|
||||
def customer_lookup(self, value=None):
|
||||
|
||||
def select_customer(uuid):
|
||||
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)
|
||||
customer = session.get(self.model.Customer, uuid)
|
||||
self.set_customer(customer)
|
||||
session.commit()
|
||||
session.close()
|
||||
|
@ -183,88 +105,11 @@ class POSView(WuttaView):
|
|||
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,
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
content=WuttaCustomerLookup(self.config, initial_search=value,
|
||||
on_customer=select_customer, on_cancel=cancel),
|
||||
)
|
||||
|
||||
self.page.dialog = dlg
|
||||
|
@ -359,7 +204,8 @@ class POSView(WuttaView):
|
|||
|
||||
entry = self.main_input.value
|
||||
if entry:
|
||||
self.attempt_set_customer(entry)
|
||||
if not self.attempt_set_customer(entry):
|
||||
self.customer_lookup(entry)
|
||||
else:
|
||||
self.customer_lookup()
|
||||
|
||||
|
@ -435,6 +281,7 @@ class POSView(WuttaView):
|
|||
|
||||
session.commit()
|
||||
session.close()
|
||||
return bool(customer)
|
||||
|
||||
def customer_click(self, e):
|
||||
|
||||
|
@ -446,7 +293,8 @@ class POSView(WuttaView):
|
|||
value = self.main_input.value
|
||||
if value:
|
||||
# okay try to set it with given value
|
||||
self.attempt_set_customer(value)
|
||||
if not self.attempt_set_customer(value):
|
||||
self.customer_lookup(value)
|
||||
|
||||
else:
|
||||
# no value provided, so do lookup
|
||||
|
@ -977,10 +825,7 @@ class POSView(WuttaView):
|
|||
self.page.session.set('txn_display', None)
|
||||
self.page.session.set('cust_uuid', None)
|
||||
self.page.session.set('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()
|
||||
self.informed_refresh()
|
||||
|
||||
def main_submit(self, e=None):
|
||||
value = self.main_input.value
|
||||
|
|
Loading…
Reference in a new issue