Add support for "void line"
also abstract the rendering of a line item to separate class, so can override later etc.
This commit is contained in:
parent
d969d5c994
commit
7a8dce69b3
93
wuttapos/controls/txnitem.py
Normal file
93
wuttapos/controls/txnitem.py
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# -*- 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 - txn item control
|
||||||
|
"""
|
||||||
|
|
||||||
|
import flet as ft
|
||||||
|
|
||||||
|
from .base import WuttaControl
|
||||||
|
|
||||||
|
|
||||||
|
class WuttaTxnItem(WuttaControl):
|
||||||
|
"""
|
||||||
|
Control for displaying a transaction line item within main POS
|
||||||
|
items list.
|
||||||
|
"""
|
||||||
|
font_size = 24
|
||||||
|
|
||||||
|
def __init__(self, config, row, *args, **kwargs):
|
||||||
|
super().__init__(config, *args, **kwargs)
|
||||||
|
self.row = row
|
||||||
|
|
||||||
|
def build(self):
|
||||||
|
|
||||||
|
kw = {}
|
||||||
|
if self.row.void:
|
||||||
|
kw['color'] = 'red'
|
||||||
|
kw['decoration'] = ft.TextDecoration.LINE_THROUGH
|
||||||
|
|
||||||
|
self.major_style = ft.TextStyle(size=self.font_size,
|
||||||
|
weight=ft.FontWeight.BOLD,
|
||||||
|
**kw)
|
||||||
|
|
||||||
|
self.minor_style = ft.TextStyle(size=int(self.font_size * 0.8),
|
||||||
|
italic=True,
|
||||||
|
**kw)
|
||||||
|
|
||||||
|
quantity = self.app.render_quantity(self.row.quantity)
|
||||||
|
pretty_price = self.app.render_currency(self.row.txn_price)
|
||||||
|
|
||||||
|
return ft.Row(
|
||||||
|
[
|
||||||
|
ft.Row([
|
||||||
|
|
||||||
|
ft.Text(
|
||||||
|
spans=[
|
||||||
|
ft.TextSpan(f"{self.row.description}",
|
||||||
|
style=self.major_style),
|
||||||
|
ft.TextSpan(f"× {quantity} @ {pretty_price}",
|
||||||
|
style=self.minor_style),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
ft.Text(
|
||||||
|
spans=[
|
||||||
|
ft.TextSpan(self.app.render_currency(self.row.sales_total),
|
||||||
|
style=self.major_style),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||||
|
)
|
||||||
|
|
||||||
|
def mark_void(self):
|
||||||
|
|
||||||
|
self.major_style.color = 'red'
|
||||||
|
self.major_style.decoration = ft.TextDecoration.LINE_THROUGH
|
||||||
|
|
||||||
|
self.minor_style.color = 'red'
|
||||||
|
self.minor_style.decoration = ft.TextDecoration.LINE_THROUGH
|
||||||
|
|
||||||
|
self.update()
|
|
@ -33,6 +33,7 @@ import flet as ft
|
||||||
from .base import WuttaView
|
from .base import WuttaView
|
||||||
from wuttapos.controls.custlookup import WuttaCustomerLookup
|
from wuttapos.controls.custlookup import WuttaCustomerLookup
|
||||||
from wuttapos.controls.itemlookup import WuttaProductLookup
|
from wuttapos.controls.itemlookup import WuttaProductLookup
|
||||||
|
from wuttapos.controls.txnitem import WuttaTxnItem
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -63,22 +64,24 @@ class POSView(WuttaView):
|
||||||
for control in self.informed_controls:
|
for control in self.informed_controls:
|
||||||
control.informed_refresh()
|
control.informed_refresh()
|
||||||
|
|
||||||
def reset(self, e=None, clear_quantity=True, update=True):
|
def reset(self, e=None, clear_quantity=True):
|
||||||
"""
|
"""
|
||||||
This is a convenience method, meant only to clear the main
|
This is a convenience method, meant only to clear the main
|
||||||
input and set focus to it. Will also update() the page
|
input and set focus to it. Will also update() the page.
|
||||||
by default.
|
|
||||||
|
|
||||||
The ``e`` arg is ignored and accepted only so this method may
|
The ``e`` arg is ignored and accepted only so this method may
|
||||||
be registered as an event handler, e.g. ``on_cancel``.
|
be registered as an event handler, e.g. ``on_cancel``.
|
||||||
"""
|
"""
|
||||||
|
# clear set (@) quantity
|
||||||
if clear_quantity:
|
if clear_quantity:
|
||||||
self.set_quantity.data = None
|
self.set_quantity.data = None
|
||||||
self.set_quantity.value = None
|
self.set_quantity.value = None
|
||||||
|
|
||||||
|
# clear/focus main input
|
||||||
self.main_input.value = ''
|
self.main_input.value = ''
|
||||||
self.main_input.focus()
|
self.main_input.focus()
|
||||||
if update:
|
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
def set_customer(self, customer, batch=None):
|
def set_customer(self, customer, batch=None):
|
||||||
session = self.app.get_session(customer)
|
session = self.app.get_session(customer)
|
||||||
|
@ -137,7 +140,7 @@ class POSView(WuttaView):
|
||||||
if row:
|
if row:
|
||||||
session.commit()
|
session.commit()
|
||||||
self.add_row_item(row)
|
self.add_row_item(row)
|
||||||
self.items.scroll_to(offset=-1, duration=250)
|
self.items.scroll_to(offset=-1, duration=100)
|
||||||
self.txn_total.value = self.app.render_currency(batch.get_balance())
|
self.txn_total.value = self.app.render_currency(batch.get_balance())
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
|
@ -490,7 +493,9 @@ class POSView(WuttaView):
|
||||||
text_style=ft.TextStyle(weight=ft.FontWeight.BOLD),
|
text_style=ft.TextStyle(weight=ft.FontWeight.BOLD),
|
||||||
autofocus=True)
|
autofocus=True)
|
||||||
|
|
||||||
|
self.selected_item = None
|
||||||
self.items = ft.ListView(
|
self.items = ft.ListView(
|
||||||
|
item_extent=50,
|
||||||
height=800,
|
height=800,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -525,6 +530,9 @@ class POSView(WuttaView):
|
||||||
elif self.set_quantity.data is not None:
|
elif self.set_quantity.data is not None:
|
||||||
self.set_quantity.data = None
|
self.set_quantity.data = None
|
||||||
self.set_quantity.value = None
|
self.set_quantity.value = None
|
||||||
|
elif self.selected_item:
|
||||||
|
self.selected_item.bgcolor = 'white'
|
||||||
|
self.selected_item = None
|
||||||
self.main_input.focus()
|
self.main_input.focus()
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
|
@ -566,12 +574,30 @@ class POSView(WuttaView):
|
||||||
|
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
elif value == '↑':
|
elif value == '↑': # UP
|
||||||
self.items.scroll_to(delta=-50, duration=250)
|
|
||||||
|
# select previous item, if selection in progress
|
||||||
|
if self.selected_item:
|
||||||
|
i = self.items.controls.index(self.selected_item)
|
||||||
|
if i > 0:
|
||||||
|
self.items.scroll_to(delta=-50, duration=100)
|
||||||
|
self.select_txn_item(self.items.controls[i - 1])
|
||||||
|
return
|
||||||
|
|
||||||
|
self.items.scroll_to(delta=-50, duration=100)
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
elif value == '↓':
|
elif value == '↓':
|
||||||
self.items.scroll_to(delta=50, duration=250)
|
|
||||||
|
# select next item, if selection in progress
|
||||||
|
if self.selected_item:
|
||||||
|
i = self.items.controls.index(self.selected_item)
|
||||||
|
if (i + 1) < len(self.items.controls):
|
||||||
|
self.items.scroll_to(delta=50, duration=100)
|
||||||
|
self.select_txn_item(self.items.controls[i + 1])
|
||||||
|
return
|
||||||
|
|
||||||
|
self.items.scroll_to(delta=50, duration=100)
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -580,11 +606,11 @@ class POSView(WuttaView):
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
def up_long_press(e):
|
def up_long_press(e):
|
||||||
self.items.scroll_to(delta=-500, duration=250)
|
self.items.scroll_to(delta=-500, duration=100)
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
def down_long_press(e):
|
def down_long_press(e):
|
||||||
self.items.scroll_to(delta=500, duration=250)
|
self.items.scroll_to(delta=500, duration=100)
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
tenkey_button_size = self.default_button_size
|
tenkey_button_size = self.default_button_size
|
||||||
|
@ -894,25 +920,26 @@ class POSView(WuttaView):
|
||||||
if row.row_type not in ('sell',):
|
if row.row_type not in ('sell',):
|
||||||
return
|
return
|
||||||
|
|
||||||
quantity = self.app.render_quantity(row.quantity)
|
|
||||||
pretty_price = self.app.render_currency(row.txn_price)
|
|
||||||
self.items.controls.append(
|
self.items.controls.append(
|
||||||
ft.Container(
|
ft.Container(
|
||||||
content=ft.Row(
|
content=WuttaTxnItem(self.config, row),
|
||||||
[
|
|
||||||
ft.Row([
|
|
||||||
self.make_text(f"{row.description}"),
|
|
||||||
self.make_text(f"× {quantity} @ {pretty_price}",
|
|
||||||
weight=None, italic=True, size=20),
|
|
||||||
]),
|
|
||||||
self.make_text(self.app.render_currency(row.sales_total)),
|
|
||||||
],
|
|
||||||
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),
|
||||||
|
on_click=self.list_item_click,
|
||||||
|
data={'row': row},
|
||||||
|
key=row.uuid,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def list_item_click(self, e):
|
||||||
|
self.select_txn_item(e.control)
|
||||||
|
|
||||||
|
def select_txn_item(self, item):
|
||||||
|
if self.selected_item:
|
||||||
|
self.selected_item.bgcolor = 'white'
|
||||||
|
|
||||||
|
item.bgcolor = 'blue'
|
||||||
|
self.selected_item = item
|
||||||
|
self.page.update()
|
||||||
|
|
||||||
def void_click(self, e):
|
def void_click(self, e):
|
||||||
|
|
||||||
|
@ -924,12 +951,39 @@ class POSView(WuttaView):
|
||||||
user = self.get_current_user(session)
|
user = self.get_current_user(session)
|
||||||
batch = handler.get_current_batch(user, create=True)
|
batch = handler.get_current_batch(user, create=True)
|
||||||
|
|
||||||
# void current batch
|
if self.selected_item:
|
||||||
handler.void_batch(batch, user)
|
|
||||||
|
# void line
|
||||||
|
row = self.selected_item.data['row']
|
||||||
|
if not row.void:
|
||||||
|
row = session.get(row.__class__, row.uuid)
|
||||||
|
handler.void_row(row, user)
|
||||||
|
self.selected_item.data['row'] = row
|
||||||
|
self.selected_item.content.mark_void()
|
||||||
|
self.selected_item.bgcolor = 'white'
|
||||||
|
self.selected_item = None
|
||||||
|
|
||||||
|
# update screen to reflect new balance
|
||||||
|
self.txn_total.value = self.app.render_currency(batch.get_balance())
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
# void txn
|
||||||
|
handler.void_batch(batch, user)
|
||||||
|
self.clear_all()
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
self.clear_all()
|
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def cancel(e):
|
def cancel(e):
|
||||||
|
@ -942,11 +996,13 @@ class POSView(WuttaView):
|
||||||
|
|
||||||
if batch:
|
if batch:
|
||||||
|
|
||||||
|
# prompt to void something
|
||||||
|
target = 'LINE' if self.selected_item else 'TXN'
|
||||||
dlg = ft.AlertDialog(
|
dlg = ft.AlertDialog(
|
||||||
title=ft.Text("Confirm VOID"),
|
title=ft.Text("Confirm VOID"),
|
||||||
content=ft.Text("Really VOID this transaction?"),
|
content=ft.Text(f"Really VOID {target}?"),
|
||||||
actions=[
|
actions=[
|
||||||
ft.Container(content=ft.Text("Yes, VOID",
|
ft.Container(content=ft.Text(f"VOID {target}",
|
||||||
size=self.default_font_size,
|
size=self.default_font_size,
|
||||||
color='black',
|
color='black',
|
||||||
weight=ft.FontWeight.BOLD),
|
weight=ft.FontWeight.BOLD),
|
||||||
|
|
Loading…
Reference in a new issue