From 07b1d0841efce1234052fc89e043388e5c8018d4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 7 Oct 2023 16:26:33 -0500 Subject: [PATCH] Improve views for taxes, esp. in POS batches --- tailbone/grids/filters.py | 11 ++++- tailbone/templates/batch/pos/view.mako | 13 ++++++ tailbone/views/batch/pos.py | 60 ++++++++++++++++++++++---- tailbone/views/master.py | 8 ++++ tailbone/views/products.py | 13 +++++- tailbone/views/taxes.py | 24 ++++++++--- tailbone/views/typical.py | 1 + 7 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 tailbone/templates/batch/pos/view.mako diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py index c8815f9f..61d29554 100644 --- a/tailbone/grids/filters.py +++ b/tailbone/grids/filters.py @@ -177,13 +177,18 @@ class GridFilter(object): self.key = key self.config = config self.label = label or prettify(key) - self.verbs = verbs or self.get_default_verbs() + if value_renderer: self.set_value_renderer(value_renderer) elif value_enum: self.set_choices(value_enum) else: self.set_value_renderer(self.value_renderer_factory) + + # nb. do this after setting choices, if applicable, since that + # could change default verbs + self.verbs = verbs or self.get_default_verbs() + self.default_active = default_active self.default_verb = default_verb self.default_value = default_value @@ -461,6 +466,10 @@ class AlchemyStringFilter(AlchemyGridFilter): """ Expose contains / does-not-contain verbs in addition to core. """ + + if self.choices: + return ['equal', 'not_equal', 'is_null', 'is_not_null', 'is_any'] + return ['contains', 'does_not_contain', 'contains_any_of', 'equal', 'not_equal', 'equal_any_of', diff --git a/tailbone/templates/batch/pos/view.mako b/tailbone/templates/batch/pos/view.mako new file mode 100644 index 00000000..0da755aa --- /dev/null +++ b/tailbone/templates/batch/pos/view.mako @@ -0,0 +1,13 @@ +## -*- coding: utf-8; -*- +<%inherit file="/batch/view.mako" /> + +<%def name="modify_this_page_vars()"> + ${parent.modify_this_page_vars()} + + + +${parent.body()} diff --git a/tailbone/views/batch/pos.py b/tailbone/views/batch/pos.py index 8bc70b02..00f1603f 100644 --- a/tailbone/views/batch/pos.py +++ b/tailbone/views/batch/pos.py @@ -26,6 +26,8 @@ Views for POS batches from rattail.db.model import POSBatch, POSBatchRow +from webhelpers2.html import HTML + from tailbone.views.batch import BatchMasterView @@ -39,7 +41,11 @@ class POSBatchView(BatchMasterView): route_prefix = 'batch.pos' url_prefix = '/batch/pos' creatable = False + editable = False cloneable = True + refreshable = False + rows_deletable = False + rows_bulk_deletable = False labels = { 'terminal_id': "Terminal ID", @@ -66,8 +72,7 @@ class POSBatchView(BatchMasterView): 'params', 'rowcount', 'sales_total', - 'tax1_total', - 'tax2_total', + 'taxes', 'tender_total', 'balance', 'void', @@ -89,6 +94,7 @@ class POSBatchView(BatchMasterView): 'quantity', 'sales_total', 'tender_total', + 'tax_code', 'user', ] @@ -102,8 +108,7 @@ class POSBatchView(BatchMasterView): 'txn_price', 'quantity', 'sales_total', - 'tax1_total', - 'tax2_total', + 'tax_code', 'tender_total', 'tender', 'void', @@ -126,8 +131,6 @@ class POSBatchView(BatchMasterView): g.set_link('created_by') g.set_type('sales_total', 'currency') - g.set_type('tax1_total', 'currency') - g.set_type('tax2_total', 'currency') g.set_type('tender_total', 'currency') # executed @@ -149,13 +152,54 @@ class POSBatchView(BatchMasterView): f.set_renderer('customer', self.render_customer) f.set_type('sales_total', 'currency') - f.set_type('tax1_total', 'currency') - f.set_type('tax2_total', 'currency') f.set_type('tender_total', 'currency') f.set_type('tender_total', 'currency') + f.set_renderer('taxes', self.render_taxes) + f.set_renderer('balance', lambda batch, field: app.render_currency(batch.get_balance())) + def render_taxes(self, batch, field): + route_prefix = self.get_route_prefix() + + factory = self.get_grid_factory() + g = factory( + key=f'{route_prefix}.taxes', + data=[], + columns=[ + 'code', + 'description', + 'rate', + 'total', + ], + ) + + return HTML.literal( + g.render_buefy_table_element(data_prop='taxesData')) + + def template_kwargs_view(self, **kwargs): + kwargs = super().template_kwargs_view(**kwargs) + app = self.get_rattail_app() + batch = kwargs['instance'] + + taxes = [] + for btax in batch.taxes.values(): + data = { + 'uuid': btax.uuid, + 'code': btax.tax_code, + 'description': btax.tax.description, + 'rate': app.render_percent(btax.tax_rate), + 'total': app.render_currency(btax.tax_total), + } + taxes.append(data) + taxes.sort(key=lambda t: t['code']) + kwargs['taxes_data'] = taxes + + kwargs['execute_enabled'] = False + kwargs['why_not_execute'] = "POS batch must be executed at POS" + + return kwargs + def configure_row_grid(self, g): super().configure_row_grid(g) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index e3a60eca..26936a71 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -861,6 +861,14 @@ class MasterView(View): url = self.request.route_url('stores.view', uuid=store.uuid) return tags.link_to(text, url) + def render_tax(self, obj, field): + tax = getattr(obj, field) + if not tax: + return + text = str(tax) + url = self.request.route_url('taxes.view', uuid=tax.uuid) + return tags.link_to(text, url) + def render_tender(self, obj, field): tender = getattr(obj, field) if not tender: diff --git a/tailbone/views/products.py b/tailbone/views/products.py index 0ee53093..327b6366 100644 --- a/tailbone/views/products.py +++ b/tailbone/views/products.py @@ -366,6 +366,15 @@ class ProductView(MasterView): g.set_renderer('cost', self.render_cost) g.set_label('cost', "Unit Cost") + # tax + g.set_joiner('tax', lambda q: q.outerjoin(model.Tax)) + taxes = self.Session.query(model.Tax)\ + .order_by(model.Tax.code)\ + .all() + taxes = OrderedDict([(tax.uuid, tax.description) + for tax in taxes]) + g.set_filter('tax', model.Tax.uuid, value_enum=taxes) + # report_code_name g.set_joiner('report_code_name', lambda q: q.outerjoin(model.ReportCode)) g.set_filter('report_code_name', model.ReportCode.name) @@ -810,7 +819,7 @@ class ProductView(MasterView): raise self.notfound() def configure_form(self, f): - super(ProductView, self).configure_form(f) + super().configure_form(f) product = f.model_instance # department @@ -934,7 +943,7 @@ class ProductView(MasterView): f.set_label('tax_uuid', "Tax") else: f.set_readonly('tax') - # f.set_renderer('tax', self.render_tax) + f.set_renderer('tax', self.render_tax) # tax1/2/3 f.set_readonly('tax1') diff --git a/tailbone/views/taxes.py b/tailbone/views/taxes.py index 19a385ba..b2afaeb9 100644 --- a/tailbone/views/taxes.py +++ b/tailbone/views/taxes.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2022 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -24,8 +24,6 @@ Tax Views """ -from __future__ import unicode_literals, absolute_import - from rattail.db import model from tailbone.views import MasterView @@ -53,12 +51,26 @@ class TaxView(MasterView): ] def configure_grid(self, g): - super(TaxView, self).configure_grid(g) - g.filters['description'].default_active = True - g.filters['description'].default_verb = 'contains' + super().configure_grid(g) + + # code g.set_sort_defaults('code') g.set_link('code') + + # description g.set_link('description') + g.filters['description'].default_active = True + g.filters['description'].default_verb = 'contains' + + # rate + g.set_type('rate', 'percent') + + def configure_form(self, f): + super().configure_form(f) + + # rate + f.set_type('rate', 'percent') + # TODO: deprecate / remove this TaxesView = TaxView diff --git a/tailbone/views/typical.py b/tailbone/views/typical.py index ed94d552..35259a14 100644 --- a/tailbone/views/typical.py +++ b/tailbone/views/typical.py @@ -43,6 +43,7 @@ def defaults(config, **kwargs): config.include(mod('tailbone.views.reportcodes')) config.include(mod('tailbone.views.stores')) config.include(mod('tailbone.views.subdepartments')) + config.include(mod('tailbone.views.taxes')) config.include(mod('tailbone.views.tenders')) config.include(mod('tailbone.views.uoms')) config.include(mod('tailbone.views.vendors'))