Compare commits

...

4 commits

Author SHA1 Message Date
Lance Edgar edd1f17184 fix: fix customer rendering in OrderItem grids; add sort/filter 2025-02-20 09:04:12 -06:00
Lance Edgar d8b37969c5 fix: track vendor name/SKU per OrderItem
and include vendor name filter by default for Placement, Receiving views
2025-02-20 09:04:10 -06:00
Lance Edgar 7ea83b2715 fix: require store for new orders, if so configured 2025-02-19 19:18:30 -06:00
Lance Edgar f3cca2e370 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
2025-02-19 19:15:50 -06:00
8 changed files with 135 additions and 2 deletions

View file

@ -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"]

View file

@ -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
@ -1017,8 +1021,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"
@ -1190,6 +1200,8 @@ class NewOrderBatchHandler(BatchHandler):
'product_weighed',
'department_id',
'department_name',
'vendor_name',
'vendor_item_code',
'case_size',
'order_qty',
'order_uom',

View file

@ -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')

View file

@ -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.

View file

@ -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.
""")

View file

@ -243,6 +243,12 @@
<b-field horizontal label="Special Order">
<span>${app.render_boolean(item.special_order)}</span>
</b-field>
<b-field horizontal label="Vendor Name">
<span>${item.vendor_name}</span>
</b-field>
<b-field horizontal label="Vendor Item Code">
<span>${item.vendor_item_code}</span>
</b-field>
</div>
</div>
</nav>

View file

@ -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)
@ -1562,6 +1565,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 +1687,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)

View file

@ -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)
@ -1114,9 +1118,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