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:
Lance Edgar 2023-09-30 21:08:29 -05:00
parent d969d5c994
commit 7a8dce69b3
2 changed files with 179 additions and 30 deletions

View 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()

View file

@ -33,6 +33,7 @@ import flet as ft
from .base import WuttaView
from wuttapos.controls.custlookup import WuttaCustomerLookup
from wuttapos.controls.itemlookup import WuttaProductLookup
from wuttapos.controls.txnitem import WuttaTxnItem
log = logging.getLogger(__name__)
@ -63,21 +64,23 @@ class POSView(WuttaView):
for control in self.informed_controls:
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
input and set focus to it. Will also update() the page
by default.
input and set focus to it. Will also update() the page.
The ``e`` arg is ignored and accepted only so this method may
be registered as an event handler, e.g. ``on_cancel``.
"""
# clear set (@) quantity
if clear_quantity:
self.set_quantity.data = None
self.set_quantity.value = None
# clear/focus main input
self.main_input.value = ''
self.main_input.focus()
if update:
self.page.update()
def set_customer(self, customer, batch=None):
@ -137,7 +140,7 @@ class POSView(WuttaView):
if row:
session.commit()
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.reset()
@ -490,7 +493,9 @@ class POSView(WuttaView):
text_style=ft.TextStyle(weight=ft.FontWeight.BOLD),
autofocus=True)
self.selected_item = None
self.items = ft.ListView(
item_extent=50,
height=800,
)
@ -525,6 +530,9 @@ class POSView(WuttaView):
elif self.set_quantity.data is not None:
self.set_quantity.data = None
self.set_quantity.value = None
elif self.selected_item:
self.selected_item.bgcolor = 'white'
self.selected_item = None
self.main_input.focus()
self.page.update()
@ -566,12 +574,30 @@ class POSView(WuttaView):
self.page.update()
elif value == '':
self.items.scroll_to(delta=-50, duration=250)
elif value == '': # UP
# 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()
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()
else:
@ -580,11 +606,11 @@ class POSView(WuttaView):
self.page.update()
def up_long_press(e):
self.items.scroll_to(delta=-500, duration=250)
self.items.scroll_to(delta=-500, duration=100)
self.page.update()
def down_long_press(e):
self.items.scroll_to(delta=500, duration=250)
self.items.scroll_to(delta=500, duration=100)
self.page.update()
tenkey_button_size = self.default_button_size
@ -894,25 +920,26 @@ class POSView(WuttaView):
if row.row_type not in ('sell',):
return
quantity = self.app.render_quantity(row.quantity)
pretty_price = self.app.render_currency(row.txn_price)
self.items.controls.append(
ft.Container(
content=ft.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,
),
content=WuttaTxnItem(self.config, row),
border=ft.border.only(bottom=ft.border.BorderSide(1, 'gray')),
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):
@ -924,12 +951,39 @@ class POSView(WuttaView):
user = self.get_current_user(session)
batch = handler.get_current_batch(user, create=True)
# void current batch
if self.selected_item:
# 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.close()
self.clear_all()
self.reset()
def cancel(e):
@ -942,11 +996,13 @@ class POSView(WuttaView):
if batch:
# 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",
ft.Container(content=ft.Text(f"VOID {target}",
size=self.default_font_size,
color='black',
weight=ft.FontWeight.BOLD),