From ebd22fe6ee8b76eae30b2197e4f78b37df95c080 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 6 Jan 2025 19:46:43 -0600 Subject: [PATCH] fix: expose config for new order w/ pending product - whether or not this feature should be allowed at all - user must have permission also - UI should honor both of the above - also, which fields are required for new pending product --- src/sideshow/batch/neworder.py | 14 +++++ .../web/templates/orders/configure.mako | 41 ++++++++++++ src/sideshow/web/templates/orders/create.mako | 33 ++++------ src/sideshow/web/views/orders.py | 62 ++++++++++++++++--- tests/batch/test_neworder.py | 55 ++++++++++++++++ 5 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 src/sideshow/web/templates/orders/configure.mako diff --git a/src/sideshow/batch/neworder.py b/src/sideshow/batch/neworder.py index 5ec9b8d..786a1e0 100644 --- a/src/sideshow/batch/neworder.py +++ b/src/sideshow/batch/neworder.py @@ -44,6 +44,14 @@ class NewOrderBatchHandler(BatchHandler): """ model_class = NewOrderBatch + def allow_unknown_product(self): + """ + Returns a boolean indicating whether "unknown" (pending) + products are allowed when creating a new order. + """ + return self.config.get_bool('sideshow.orders.allow_unknown_product', + default=True) + def set_pending_customer(self, batch, data): """ Set (add or update) pending customer info for the batch. @@ -122,6 +130,9 @@ class NewOrderBatchHandler(BatchHandler): :class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow` which was added to the batch. """ + if not self.allow_unknown_product(): + raise TypeError("unknown/pending product not allowed for new orders") + model = self.app.model enum = self.app.enum session = self.app.get_session(batch) @@ -167,6 +178,9 @@ class NewOrderBatchHandler(BatchHandler): :param data: Dict of field data for the :class:`~sideshow.db.model.products.PendingProduct` record. """ + if not self.allow_unknown_product(): + raise TypeError("unknown/pending product not allowed for new orders") + model = self.app.model enum = self.app.enum session = self.app.get_session(row) diff --git a/src/sideshow/web/templates/orders/configure.mako b/src/sideshow/web/templates/orders/configure.mako new file mode 100644 index 0000000..044d1fd --- /dev/null +++ b/src/sideshow/web/templates/orders/configure.mako @@ -0,0 +1,41 @@ +## -*- coding: utf-8; -*- +<%inherit file="/configure.mako" /> + +<%def name="form_content()"> + +

Products

+
+ + + + Allow creating orders for "unknown" products + + + +
+ +

+ Require these fields for new product: +

+ +
+ % for field in pending_product_fields: + + + ${field} + + + % endfor +
+ +
+
+ diff --git a/src/sideshow/web/templates/orders/create.mako b/src/sideshow/web/templates/orders/create.mako index 7763775..967a803 100644 --- a/src/sideshow/web/templates/orders/create.mako +++ b/src/sideshow/web/templates/orders/create.mako @@ -402,8 +402,8 @@
- - {{ productKey }} + + {{ productScancode }} @@ -493,12 +493,12 @@
- - + - <${b}-table-column :label="productKeyLabel" + <${b}-table-column label="Scancode" v-slot="props"> - {{ props.row.product_key }} + {{ props.row.product_scancode }} <${b}-table-column label="Brand" @@ -876,16 +876,11 @@ itemDialogTabIndex: 0, % endif - ## TODO - productIsKnown: false, - + productIsKnown: true, selectedProduct: null, productID: null, productDisplay: null, - ## TODO - productKey: null, - productKeyField: 'scancode', - productKeyLabel: "Scancode", + productScancode: null, productSize: null, productCaseQuantity: null, productUnitPrice: null, @@ -1341,13 +1336,11 @@ showAddItemDialog() { this.customerPanelOpen = false this.editingItem = null - // TODO - // this.productIsKnown = true - this.productIsKnown = false + this.productIsKnown = true ## this.selectedProduct = null this.productID = null this.productDisplay = null - ## this.productKey = null + this.productScancode = null this.productSize = null this.productCaseQuantity = null this.productUnitPrice = null @@ -1402,7 +1395,7 @@ this.pendingProduct = pending this.productDisplay = row.product_full_description - this.productKey = row.product_key + this.productScancode = row.product_scancode this.productSize = row.product_size this.productCaseQuantity = row.case_quantity this.productURL = row.product_url diff --git a/src/sideshow/web/views/orders.py b/src/sideshow/web/views/orders.py index 76b49f3..ae580fe 100644 --- a/src/sideshow/web/views/orders.py +++ b/src/sideshow/web/views/orders.py @@ -58,6 +58,7 @@ class OrderView(MasterView): """ model_class = Order editable = False + configurable = True labels = { 'order_id': "Order ID", @@ -122,15 +123,14 @@ class OrderView(MasterView): PENDING_PRODUCT_ENTRY_FIELDS = [ 'scancode', - 'department_id', - 'department_name', 'brand_name', 'description', 'size', + 'department_name', 'vendor_name', 'vendor_item_code', - 'unit_cost', 'case_size', + 'unit_cost', 'unit_price_reg', ] @@ -213,10 +213,10 @@ class OrderView(MasterView): 'normalized_batch': self.normalize_batch(batch), 'order_items': [self.normalize_row(row) for row in batch.rows], - - 'allow_unknown_product': True, # TODO 'default_uom_choices': self.get_default_uom_choices(), 'default_uom': None, # TODO? + 'allow_unknown_product': (self.batch_handler.allow_unknown_product() + and self.has_perm('create_unknown_product')), 'pending_product_required_fields': self.get_pending_product_required_fields(), }) return self.render_to_response('create', context) @@ -567,9 +567,6 @@ class OrderView(MasterView): 'special_order': pending.special_order, } - # TODO: remove this - data['product_key'] = row.product_scancode - # display text for order qty/uom if row.order_uom == enum.ORDER_UOM_CASE: if row.case_size is None: @@ -670,6 +667,55 @@ class OrderView(MasterView): """ """ return self.request.route_url('order_items.view', uuid=item.uuid) + def configure_get_simple_settings(self): + """ """ + settings = [ + + # products + {'name': 'sideshow.orders.allow_unknown_product', + 'type': bool, + 'default': True}, + ] + + # required fields for new product entry + for field in self.PENDING_PRODUCT_ENTRY_FIELDS: + setting = {'name': f'sideshow.orders.unknown_product.fields.{field}.required', + 'type': bool} + if field == 'description': + setting['default'] = True + settings.append(setting) + + return settings + + def configure_get_context(self, **kwargs): + """ """ + context = super().configure_get_context(**kwargs) + + context['pending_product_fields'] = self.PENDING_PRODUCT_ENTRY_FIELDS + + return context + + @classmethod + def defaults(cls, config): + cls._order_defaults(config) + cls._defaults(config) + + @classmethod + def _order_defaults(cls, config): + permission_prefix = cls.get_permission_prefix() + model_title = cls.get_model_title() + model_title_plural = cls.get_model_title_plural() + + # fix perm group + config.add_wutta_permission_group(permission_prefix, + model_title_plural, + overwrite=False) + + # extra perm required to create order with unknown/pending product + config.add_wutta_permission(permission_prefix, + f'{permission_prefix}.create_unknown_product', + f"Create new {model_title} for unknown/pending product") + class OrderItemView(MasterView): """ diff --git a/tests/batch/test_neworder.py b/tests/batch/test_neworder.py index 66e625e..757a2dc 100644 --- a/tests/batch/test_neworder.py +++ b/tests/batch/test_neworder.py @@ -1,5 +1,6 @@ # -*- coding: utf-8; -*- +import datetime import decimal from wuttjamaican.testing import DataTestCase @@ -18,6 +19,16 @@ class TestNewOrderBatchHandler(DataTestCase): def make_handler(self): return mod.NewOrderBatchHandler(self.config) + def tets_allow_unknown_product(self): + handler = self.make_handler() + + # true by default + self.assertTrue(handler.allow_unknown_product()) + + # config can disable + config.setdefault('sideshow.orders.allow_unknown_product', 'false') + self.assertFalse(handler.allow_unknown_product()) + def test_set_pending_customer(self): model = self.app.model handler = self.make_handler() @@ -119,6 +130,10 @@ class TestNewOrderBatchHandler(DataTestCase): self.assertEqual(product.unit_price_reg, decimal.Decimal('5.99')) self.assertIs(product.created_by, user) + # error if unknown products not allowed + self.config.setdefault('sideshow.orders.allow_unknown_product', 'false') + self.assertRaises(TypeError, handler.add_pending_product, batch, kw, 1, enum.ORDER_UOM_UNIT) + def test_set_pending_product(self): model = self.app.model enum = self.app.enum @@ -210,6 +225,15 @@ class TestNewOrderBatchHandler(DataTestCase): self.assertEqual(product.unit_price_reg, decimal.Decimal('3.59')) self.assertIs(product.created_by, user) + # error if unknown products not allowed + self.config.setdefault('sideshow.orders.allow_unknown_product', 'false') + self.assertRaises(TypeError, handler.set_pending_product, row, dict( + scancode='07430500116', + size='16oz', + unit_cost=decimal.Decimal('2.19'), + unit_price_reg=decimal.Decimal('3.59'), + )) + def test_refresh_row(self): model = self.app.model enum = self.app.enum @@ -310,6 +334,37 @@ class TestNewOrderBatchHandler(DataTestCase): self.assertEqual(row.case_price_quoted, decimal.Decimal('71.88')) self.assertEqual(row.total_price, decimal.Decimal('143.76')) + # refreshed from pending product (sale price) + product = model.PendingProduct(scancode='07430500132', + brand_name='Bragg', + description='Vinegar', + size='32oz', + case_size=12, + unit_cost=decimal.Decimal('3.99'), + unit_price_reg=decimal.Decimal('5.99'), + created_by=user, + status=enum.PendingProductStatus.PENDING) + row = handler.make_row(pending_product=product, order_qty=2, order_uom=enum.ORDER_UOM_CASE, + unit_price_sale=decimal.Decimal('5.19'), + sale_ends=datetime.datetime(2099, 1, 1)) + self.assertIsNone(row.status_code) + handler.add_row(batch, row) + self.assertEqual(row.status_code, row.STATUS_OK) + self.assertIsNone(row.product_id) + self.assertIs(row.pending_product, product) + self.assertEqual(row.product_scancode, '07430500132') + self.assertEqual(row.product_brand, 'Bragg') + self.assertEqual(row.product_description, 'Vinegar') + self.assertEqual(row.product_size, '32oz') + self.assertEqual(row.case_size, 12) + self.assertEqual(row.unit_cost, decimal.Decimal('3.99')) + self.assertEqual(row.unit_price_reg, decimal.Decimal('5.99')) + self.assertEqual(row.unit_price_sale, decimal.Decimal('5.19')) + self.assertEqual(row.sale_ends, datetime.datetime(2099, 1, 1)) + self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.19')) + self.assertEqual(row.case_price_quoted, decimal.Decimal('62.28')) + self.assertEqual(row.total_price, decimal.Decimal('124.56')) + def test_remove_row(self): model = self.app.model enum = self.app.enum