From f3cca2e37016ec0b3045c4f06de080bdadba33ce Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 19 Feb 2025 19:14:04 -0600 Subject: [PATCH 1/4] docs: avoid latest sphinx, per recent error not sure exactly what the problem here is, but docs cannot build with sphinx 8.2 - and traceback showed sphinx-toolbox was responsible. the latter apparently is required by the `enum-tools[sphinx]` pkg hopefully we can remove this cap before long..? cf. https://www.sphinx-doc.org/en/master/changes/index.html#release-8-2-0-released-feb-18-2025 --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ff4a4bc..d951e9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,8 @@ dependencies = [ [project.optional-dependencies] postgres = ["psycopg2"] mysql = ["mysql-connector-python"] -docs = ["Sphinx", "furo", "sphinxcontrib-programoutput", "enum-tools[sphinx]"] +# TODO: remove sphinx version cap after new sphinx-toolbox release? +docs = ["Sphinx<8.2", "furo", "sphinxcontrib-programoutput", "enum-tools[sphinx]"] tests = ["pytest-cov", "tox"] From 7ea83b2715da20bf68045628e855d18e4559cf49 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 19 Feb 2025 19:18:30 -0600 Subject: [PATCH 2/4] fix: require store for new orders, if so configured --- src/sideshow/batch/neworder.py | 8 +++++++- tests/batch/test_neworder.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/sideshow/batch/neworder.py b/src/sideshow/batch/neworder.py index 28e2627..d12cfaf 100644 --- a/src/sideshow/batch/neworder.py +++ b/src/sideshow/batch/neworder.py @@ -1017,8 +1017,14 @@ class NewOrderBatchHandler(BatchHandler): def why_not_execute(self, batch, **kwargs): """ By default this checks to ensure the batch has a customer with - phone number, and at least one item. + phone number, and at least one item. It also may check to + ensure the store is assigned, if applicable. """ + if not batch.store_id: + order_handler = self.app.get_order_handler() + if order_handler.expose_store_id(): + return "Must assign the store" + if not batch.customer_name: return "Must assign the customer" diff --git a/tests/batch/test_neworder.py b/tests/batch/test_neworder.py index ed2179a..f88b9b6 100644 --- a/tests/batch/test_neworder.py +++ b/tests/batch/test_neworder.py @@ -1114,9 +1114,15 @@ class TestNewOrderBatchHandler(DataTestCase): self.session.add(row) self.session.flush() + # batch is okay to execute.. reason = handler.why_not_execute(batch) self.assertIsNone(reason) + # unless we also require store + self.config.setdefault('sideshow.orders.expose_store_id', 'true') + reason = handler.why_not_execute(batch) + self.assertEqual(reason, "Must assign the store") + def test_make_local_customer(self): model = self.app.model enum = self.app.enum From d8b37969c5f3be4ce5e4fa67bf8e3ad28971455f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 20 Feb 2025 08:59:00 -0600 Subject: [PATCH 3/4] fix: track vendor name/SKU per OrderItem and include vendor name filter by default for Placement, Receiving views --- src/sideshow/batch/neworder.py | 6 +++ .../13af2ffbc0e0_add_order_item_vendor.py | 41 +++++++++++++++++++ src/sideshow/db/model/batch/neworder.py | 10 +++++ src/sideshow/db/model/orders.py | 10 +++++ .../web/templates/order-items/view.mako | 6 +++ src/sideshow/web/views/orders.py | 40 ++++++++++++++++++ tests/batch/test_neworder.py | 4 ++ 7 files changed, 117 insertions(+) create mode 100644 src/sideshow/db/alembic/versions/13af2ffbc0e0_add_order_item_vendor.py diff --git a/src/sideshow/batch/neworder.py b/src/sideshow/batch/neworder.py index d12cfaf..1c10a4e 100644 --- a/src/sideshow/batch/neworder.py +++ b/src/sideshow/batch/neworder.py @@ -938,6 +938,8 @@ class NewOrderBatchHandler(BatchHandler): row.department_id = product.department_id row.department_name = product.department_name row.special_order = product.special_order + row.vendor_name = product.vendor_name + row.vendor_item_code = product.vendor_item_code row.case_size = product.case_size row.unit_cost = product.unit_cost row.unit_price_reg = product.unit_price_reg @@ -959,6 +961,8 @@ class NewOrderBatchHandler(BatchHandler): row.department_id = product.department_id row.department_name = product.department_name row.special_order = product.special_order + row.vendor_name = product.vendor_name + row.vendor_item_code = product.vendor_item_code row.case_size = product.case_size row.unit_cost = product.unit_cost row.unit_price_reg = product.unit_price_reg @@ -1196,6 +1200,8 @@ class NewOrderBatchHandler(BatchHandler): 'product_weighed', 'department_id', 'department_name', + 'vendor_name', + 'vendor_item_code', 'case_size', 'order_qty', 'order_uom', diff --git a/src/sideshow/db/alembic/versions/13af2ffbc0e0_add_order_item_vendor.py b/src/sideshow/db/alembic/versions/13af2ffbc0e0_add_order_item_vendor.py new file mode 100644 index 0000000..52fe552 --- /dev/null +++ b/src/sideshow/db/alembic/versions/13af2ffbc0e0_add_order_item_vendor.py @@ -0,0 +1,41 @@ +"""add order_item.vendor* + +Revision ID: 13af2ffbc0e0 +Revises: a4273360d379 +Create Date: 2025-02-19 19:36:30.308840 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import wuttjamaican.db.util + + +# revision identifiers, used by Alembic. +revision: str = '13af2ffbc0e0' +down_revision: Union[str, None] = 'a4273360d379' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + + # sideshow_batch_neworder_row + op.add_column('sideshow_batch_neworder_row', sa.Column('vendor_name', sa.String(length=50), nullable=True)) + op.add_column('sideshow_batch_neworder_row', sa.Column('vendor_item_code', sa.String(length=20), nullable=True)) + + # sideshow_order_item + op.add_column('sideshow_order_item', sa.Column('vendor_name', sa.String(length=50), nullable=True)) + op.add_column('sideshow_order_item', sa.Column('vendor_item_code', sa.String(length=20), nullable=True)) + + +def downgrade() -> None: + + # sideshow_order_item + op.drop_column('sideshow_order_item', 'vendor_item_code') + op.drop_column('sideshow_order_item', 'vendor_name') + + # sideshow_batch_neworder_row + op.drop_column('sideshow_batch_neworder_row', 'vendor_item_code') + op.drop_column('sideshow_batch_neworder_row', 'vendor_name') diff --git a/src/sideshow/db/model/batch/neworder.py b/src/sideshow/db/model/batch/neworder.py index 9121dc6..6a64372 100644 --- a/src/sideshow/db/model/batch/neworder.py +++ b/src/sideshow/db/model/batch/neworder.py @@ -252,6 +252,16 @@ class NewOrderBatchRow(model.BatchRowMixin, model.Base): normally carried by the store. Default is null. """) + vendor_name = sa.Column(sa.String(length=50), nullable=True, doc=""" + Name of vendor from which product may be purchased, if known. See + also :attr:`vendor_item_code`. + """) + + vendor_item_code = sa.Column(sa.String(length=20), nullable=True, doc=""" + Item code (SKU) to use when ordering this product from the vendor + identified by :attr:`vendor_name`, if known. + """) + case_size = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc=""" Case pack count for the product, if known. diff --git a/src/sideshow/db/model/orders.py b/src/sideshow/db/model/orders.py index 5455d01..8d01d27 100644 --- a/src/sideshow/db/model/orders.py +++ b/src/sideshow/db/model/orders.py @@ -253,6 +253,16 @@ class OrderItem(model.Base): normally carried by the store. Default is null. """) + vendor_name = sa.Column(sa.String(length=50), nullable=True, doc=""" + Name of vendor from which product may be purchased, if known. See + also :attr:`vendor_item_code`. + """) + + vendor_item_code = sa.Column(sa.String(length=20), nullable=True, doc=""" + Item code (SKU) to use when ordering this product from the vendor + identified by :attr:`vendor_name`, if known. + """) + case_size = sa.Column(sa.Numeric(precision=10, scale=4), nullable=True, doc=""" Case pack count for the product, if known. """) diff --git a/src/sideshow/web/templates/order-items/view.mako b/src/sideshow/web/templates/order-items/view.mako index cb86977..e7d85ce 100644 --- a/src/sideshow/web/templates/order-items/view.mako +++ b/src/sideshow/web/templates/order-items/view.mako @@ -243,6 +243,12 @@ ${app.render_boolean(item.special_order)} + + ${item.vendor_name} + + + ${item.vendor_item_code} + diff --git a/src/sideshow/web/views/orders.py b/src/sideshow/web/views/orders.py index 8aa7534..252274a 100644 --- a/src/sideshow/web/views/orders.py +++ b/src/sideshow/web/views/orders.py @@ -1562,6 +1562,26 @@ class PlacementView(OrderItemView): route_prefix = 'order_items_placement' url_prefix = '/placement' + grid_columns = [ + 'order_id', + 'store_id', + 'customer_name', + 'product_brand', + 'product_description', + 'product_size', + 'department_name', + 'special_order', + 'vendor_name', + 'vendor_item_code', + 'order_qty', + 'order_uom', + 'total_price', + ] + + filter_defaults = { + 'vendor_name': {'active': True}, + } + def get_query(self, session=None): """ """ query = super().get_query(session=session) @@ -1664,6 +1684,26 @@ class ReceivingView(OrderItemView): route_prefix = 'order_items_receiving' url_prefix = '/receiving' + grid_columns = [ + 'order_id', + 'store_id', + 'customer_name', + 'product_brand', + 'product_description', + 'product_size', + 'department_name', + 'special_order', + 'vendor_name', + 'vendor_item_code', + 'order_qty', + 'order_uom', + 'total_price', + ] + + filter_defaults = { + 'vendor_name': {'active': True}, + } + def get_query(self, session=None): """ """ query = super().get_query(session=session) diff --git a/tests/batch/test_neworder.py b/tests/batch/test_neworder.py index f88b9b6..c6dca19 100644 --- a/tests/batch/test_neworder.py +++ b/tests/batch/test_neworder.py @@ -818,6 +818,8 @@ class TestNewOrderBatchHandler(DataTestCase): brand_name='Bragg', description='Vinegar', size='32oz', + vendor_name='Acme Distributors', + vendor_item_code='1234', created_by=user, status=enum.PendingProductStatus.PENDING) row = handler.make_row(pending_product=product, order_qty=1, order_uom=enum.ORDER_UOM_UNIT) @@ -830,6 +832,8 @@ class TestNewOrderBatchHandler(DataTestCase): self.assertEqual(row.product_brand, 'Bragg') self.assertEqual(row.product_description, 'Vinegar') self.assertEqual(row.product_size, '32oz') + self.assertEqual(row.vendor_name, 'Acme Distributors') + self.assertEqual(row.vendor_item_code, '1234') self.assertIsNone(row.case_size) self.assertIsNone(row.unit_cost) self.assertIsNone(row.unit_price_reg) From edd1f171849d7ed8fc917eab3d7f754fa128236e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 20 Feb 2025 08:59:43 -0600 Subject: [PATCH 4/4] fix: fix customer rendering in OrderItem grids; add sort/filter --- src/sideshow/web/views/orders.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sideshow/web/views/orders.py b/src/sideshow/web/views/orders.py index 252274a..76399ff 100644 --- a/src/sideshow/web/views/orders.py +++ b/src/sideshow/web/views/orders.py @@ -1239,6 +1239,9 @@ class OrderItemView(MasterView): # customer_name g.set_label('customer_name', "Customer", column_only=True) + g.set_renderer('customer_name', self.render_order_attr) + g.set_sorter('customer_name', model.Order.customer_name) + g.set_filter('customer_name', model.Order.customer_name) # # sequence # g.set_label('sequence', "Seq.", column_only=True)