fix: consolidate some duplicated code
i feel like there was a reason i hadn't done that to begin with, but now can't recall for sure. pylint certainly thinks it is duplicated so mostly cleaning this up per its suggestion...
This commit is contained in:
parent
49261a696d
commit
4e7c8d393c
8 changed files with 402 additions and 738 deletions
6
docs/api/sideshow.web.util.rst
Normal file
6
docs/api/sideshow.web.util.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``sideshow.web.util``
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. automodule:: sideshow.web.util
|
||||||
|
:members:
|
|
@ -59,6 +59,7 @@ For an online demo see https://demo.wuttaproject.org/
|
||||||
api/sideshow.web.forms.schema
|
api/sideshow.web.forms.schema
|
||||||
api/sideshow.web.menus
|
api/sideshow.web.menus
|
||||||
api/sideshow.web.static
|
api/sideshow.web.static
|
||||||
|
api/sideshow.web.util
|
||||||
api/sideshow.web.views
|
api/sideshow.web.views
|
||||||
api/sideshow.web.views.batch
|
api/sideshow.web.views.batch
|
||||||
api/sideshow.web.views.batch.neworder
|
api/sideshow.web.views.batch.neworder
|
||||||
|
|
|
@ -33,8 +33,10 @@ from sqlalchemy.ext.declarative import declared_attr
|
||||||
|
|
||||||
from wuttjamaican.db import model
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
from sideshow.db.model.orders import OrderMixin, OrderItemMixin
|
||||||
|
|
||||||
class NewOrderBatch(model.BatchMixin, model.Base):
|
|
||||||
|
class NewOrderBatch(model.BatchMixin, OrderMixin, model.Base):
|
||||||
"""
|
"""
|
||||||
:term:`Batch <batch>` used for entering new :term:`orders <order>`
|
:term:`Batch <batch>` used for entering new :term:`orders <order>`
|
||||||
into the system. Each batch ultimately becomes an
|
into the system. Each batch ultimately becomes an
|
||||||
|
@ -73,25 +75,6 @@ class NewOrderBatch(model.BatchMixin, model.Base):
|
||||||
STATUS_OK: "ok",
|
STATUS_OK: "ok",
|
||||||
}
|
}
|
||||||
|
|
||||||
store_id = sa.Column(
|
|
||||||
sa.String(length=10),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
ID of the store to which the order pertains, if applicable.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
customer_id = sa.Column(
|
|
||||||
sa.String(length=20),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Proper account ID for the :term:`external customer` to which the
|
|
||||||
order pertains, if applicable.
|
|
||||||
|
|
||||||
See also :attr:`local_customer` and :attr:`pending_customer`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
local_customer_uuid = sa.Column(model.UUID(), nullable=True)
|
local_customer_uuid = sa.Column(model.UUID(), nullable=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
|
@ -100,6 +83,7 @@ class NewOrderBatch(model.BatchMixin, model.Base):
|
||||||
):
|
):
|
||||||
return orm.relationship(
|
return orm.relationship(
|
||||||
"LocalCustomer",
|
"LocalCustomer",
|
||||||
|
cascade_backrefs=False,
|
||||||
back_populates="new_order_batches",
|
back_populates="new_order_batches",
|
||||||
doc="""
|
doc="""
|
||||||
Reference to the
|
Reference to the
|
||||||
|
@ -118,6 +102,7 @@ class NewOrderBatch(model.BatchMixin, model.Base):
|
||||||
):
|
):
|
||||||
return orm.relationship(
|
return orm.relationship(
|
||||||
"PendingCustomer",
|
"PendingCustomer",
|
||||||
|
cascade_backrefs=False,
|
||||||
back_populates="new_order_batches",
|
back_populates="new_order_batches",
|
||||||
doc="""
|
doc="""
|
||||||
Reference to the
|
Reference to the
|
||||||
|
@ -128,40 +113,8 @@ class NewOrderBatch(model.BatchMixin, model.Base):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
customer_name = sa.Column(
|
|
||||||
sa.String(length=100),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Name for the customer account.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
phone_number = sa.Column(
|
class NewOrderBatchRow(model.BatchRowMixin, OrderItemMixin, model.Base):
|
||||||
sa.String(length=20),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Phone number for the customer.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
email_address = sa.Column(
|
|
||||||
sa.String(length=255),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Email address for the customer.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
total_price = sa.Column(
|
|
||||||
sa.Numeric(precision=10, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Full price (not including tax etc.) for all items on the order.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NewOrderBatchRow(model.BatchRowMixin, model.Base):
|
|
||||||
"""
|
"""
|
||||||
Row of data within a :class:`NewOrderBatch`. Each row ultimately
|
Row of data within a :class:`NewOrderBatch`. Each row ultimately
|
||||||
becomes an :class:`~sideshow.db.model.orders.OrderItem`.
|
becomes an :class:`~sideshow.db.model.orders.OrderItem`.
|
||||||
|
@ -212,17 +165,6 @@ class NewOrderBatchRow(model.BatchRowMixin, model.Base):
|
||||||
Dict of possible status code -> label options.
|
Dict of possible status code -> label options.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
product_id = sa.Column(
|
|
||||||
sa.String(length=20),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Proper ID for the :term:`external product` which the order item
|
|
||||||
represents, if applicable.
|
|
||||||
|
|
||||||
See also :attr:`local_product` and :attr:`pending_product`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
local_product_uuid = sa.Column(model.UUID(), nullable=True)
|
local_product_uuid = sa.Column(model.UUID(), nullable=True)
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
|
@ -231,6 +173,7 @@ class NewOrderBatchRow(model.BatchRowMixin, model.Base):
|
||||||
):
|
):
|
||||||
return orm.relationship(
|
return orm.relationship(
|
||||||
"LocalProduct",
|
"LocalProduct",
|
||||||
|
cascade_backrefs=False,
|
||||||
back_populates="new_order_batch_rows",
|
back_populates="new_order_batch_rows",
|
||||||
doc="""
|
doc="""
|
||||||
Reference to the
|
Reference to the
|
||||||
|
@ -249,6 +192,7 @@ class NewOrderBatchRow(model.BatchRowMixin, model.Base):
|
||||||
):
|
):
|
||||||
return orm.relationship(
|
return orm.relationship(
|
||||||
"PendingProduct",
|
"PendingProduct",
|
||||||
|
cascade_backrefs=False,
|
||||||
back_populates="new_order_batch_rows",
|
back_populates="new_order_batch_rows",
|
||||||
doc="""
|
doc="""
|
||||||
Reference to the
|
Reference to the
|
||||||
|
@ -259,224 +203,5 @@ class NewOrderBatchRow(model.BatchRowMixin, model.Base):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
product_scancode = sa.Column(
|
|
||||||
sa.String(length=14),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Scancode for the product, as string.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
This column allows 14 chars, so can store a full GPC with check
|
|
||||||
digit. However as of writing the actual format used here does
|
|
||||||
not matter to Sideshow logic; "anything" should work.
|
|
||||||
|
|
||||||
That may change eventually, depending on POS integration
|
|
||||||
scenarios that come up. Maybe a config option to declare
|
|
||||||
whether check digit should be included or not, etc.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
product_brand = sa.Column(
|
|
||||||
sa.String(length=100),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Brand name for the product - up to 100 chars.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
product_description = sa.Column(
|
|
||||||
sa.String(length=255),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Description for the product - up to 255 chars.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
product_size = sa.Column(
|
|
||||||
sa.String(length=30),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Size of the product, as string - up to 30 chars.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
product_weighed = sa.Column(
|
|
||||||
sa.Boolean(),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Flag indicating the product is sold by weight; default is null.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
department_id = sa.Column(
|
|
||||||
sa.String(length=10),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
ID of the department to which the product belongs, if known.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
department_name = sa.Column(
|
|
||||||
sa.String(length=30),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Name of the department to which the product belongs, if known.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
special_order = sa.Column(
|
|
||||||
sa.Boolean(),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Flag indicating the item is a "special order" - e.g. something not
|
|
||||||
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.
|
|
||||||
|
|
||||||
If this is not set, then customer cannot order a "case" of the item.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
order_qty = sa.Column(
|
|
||||||
sa.Numeric(precision=10, scale=4),
|
|
||||||
nullable=False,
|
|
||||||
doc="""
|
|
||||||
Quantity (as decimal) of product being ordered.
|
|
||||||
|
|
||||||
This must be interpreted along with :attr:`order_uom` to determine
|
|
||||||
the *complete* order quantity, e.g. "2 cases".
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
order_uom = sa.Column(
|
|
||||||
sa.String(length=10),
|
|
||||||
nullable=False,
|
|
||||||
doc="""
|
|
||||||
Code indicating the unit of measure for product being ordered.
|
|
||||||
|
|
||||||
This should be one of the codes from
|
|
||||||
:data:`~sideshow.enum.ORDER_UOM`.
|
|
||||||
|
|
||||||
Sideshow will treat :data:`~sideshow.enum.ORDER_UOM_CASE`
|
|
||||||
differently but :data:`~sideshow.enum.ORDER_UOM_UNIT` and others
|
|
||||||
are all treated the same (i.e. "unit" is assumed).
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
unit_cost = sa.Column(
|
|
||||||
sa.Numeric(precision=9, scale=5),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Cost of goods amount for one "unit" (not "case") of the product,
|
|
||||||
as decimal to 4 places.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
unit_price_reg = sa.Column(
|
|
||||||
sa.Numeric(precision=8, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Regular price for the item unit. Unless a sale is in effect,
|
|
||||||
:attr:`unit_price_quoted` will typically match this value.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
unit_price_sale = sa.Column(
|
|
||||||
sa.Numeric(precision=8, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Sale price for the item unit, if applicable. If set, then
|
|
||||||
:attr:`unit_price_quoted` will typically match this value. See
|
|
||||||
also :attr:`sale_ends`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
sale_ends = sa.Column(
|
|
||||||
sa.DateTime(timezone=True),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
End date/time for the sale in effect, if any.
|
|
||||||
|
|
||||||
This is only relevant if :attr:`unit_price_sale` is set.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
unit_price_quoted = sa.Column(
|
|
||||||
sa.Numeric(precision=8, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Quoted price for the item unit. This is the "effective" unit
|
|
||||||
price, which is used to calculate :attr:`total_price`.
|
|
||||||
|
|
||||||
This price does *not* reflect the :attr:`discount_percent`. It
|
|
||||||
normally should match either :attr:`unit_price_reg` or
|
|
||||||
:attr:`unit_price_sale`.
|
|
||||||
|
|
||||||
See also :attr:`case_price_quoted`, if applicable.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
case_price_quoted = sa.Column(
|
|
||||||
sa.Numeric(precision=8, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Quoted price for a "case" of the item, if applicable.
|
|
||||||
|
|
||||||
This is mostly for display purposes; :attr:`unit_price_quoted` is
|
|
||||||
used for calculations.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
discount_percent = sa.Column(
|
|
||||||
sa.Numeric(precision=5, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Discount percent to apply when calculating :attr:`total_price`, if
|
|
||||||
applicable.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
total_price = sa.Column(
|
|
||||||
sa.Numeric(precision=8, scale=3),
|
|
||||||
nullable=True,
|
|
||||||
doc="""
|
|
||||||
Full price (not including tax etc.) which the customer is quoted
|
|
||||||
for the order item.
|
|
||||||
|
|
||||||
This is calculated using values from:
|
|
||||||
|
|
||||||
* :attr:`unit_price_quoted`
|
|
||||||
* :attr:`order_qty`
|
|
||||||
* :attr:`order_uom`
|
|
||||||
* :attr:`case_size`
|
|
||||||
* :attr:`discount_percent`
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.pending_product or self.product_description or "")
|
return str(self.pending_product or self.product_description or "")
|
||||||
|
|
|
@ -33,36 +33,12 @@ from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
from wuttjamaican.db import model
|
from wuttjamaican.db import model
|
||||||
|
|
||||||
|
|
||||||
class Order(model.Base): # pylint: disable=too-few-public-methods
|
class OrderMixin: # pylint: disable=too-few-public-methods
|
||||||
"""
|
"""
|
||||||
Represents an :term:`order` for a customer. Each order has one or
|
Mixin class providing common columns for orders and new order
|
||||||
more :attr:`items`.
|
batches.
|
||||||
|
|
||||||
Usually, orders are created by way of a
|
|
||||||
:class:`~sideshow.db.model.batch.neworder.NewOrderBatch`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "sideshow_order"
|
|
||||||
|
|
||||||
# TODO: this feels a bit hacky yet but it does avoid problems
|
|
||||||
# showing the Orders grid for a PendingCustomer
|
|
||||||
__colanderalchemy_config__ = {
|
|
||||||
"excludes": ["items"],
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
|
||||||
|
|
||||||
order_id = sa.Column(
|
|
||||||
sa.Integer(),
|
|
||||||
nullable=False,
|
|
||||||
doc="""
|
|
||||||
Unique ID for the order.
|
|
||||||
|
|
||||||
When the order is created from New Order Batch, this order ID will
|
|
||||||
match the batch ID.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
store_id = sa.Column(
|
store_id = sa.Column(
|
||||||
sa.String(length=10),
|
sa.String(length=10),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
@ -71,16 +47,6 @@ class Order(model.Base): # pylint: disable=too-few-public-methods
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
store = orm.relationship(
|
|
||||||
"Store",
|
|
||||||
primaryjoin="Store.store_id == Order.store_id",
|
|
||||||
foreign_keys="Order.store_id",
|
|
||||||
doc="""
|
|
||||||
Reference to the :class:`~sideshow.db.model.stores.Store`
|
|
||||||
record, if applicable.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
customer_id = sa.Column(
|
customer_id = sa.Column(
|
||||||
sa.String(length=20),
|
sa.String(length=20),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
@ -92,38 +58,6 @@ class Order(model.Base): # pylint: disable=too-few-public-methods
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
local_customer_uuid = model.uuid_fk_column(
|
|
||||||
"sideshow_customer_local.uuid", nullable=True
|
|
||||||
)
|
|
||||||
local_customer = orm.relationship(
|
|
||||||
"LocalCustomer",
|
|
||||||
cascade_backrefs=False,
|
|
||||||
back_populates="orders",
|
|
||||||
doc="""
|
|
||||||
Reference to the
|
|
||||||
:class:`~sideshow.db.model.customers.LocalCustomer` record
|
|
||||||
for the order, if applicable.
|
|
||||||
|
|
||||||
See also :attr:`customer_id` and :attr:`pending_customer`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
pending_customer_uuid = model.uuid_fk_column(
|
|
||||||
"sideshow_customer_pending.uuid", nullable=True
|
|
||||||
)
|
|
||||||
pending_customer = orm.relationship(
|
|
||||||
"PendingCustomer",
|
|
||||||
cascade_backrefs=False,
|
|
||||||
back_populates="orders",
|
|
||||||
doc="""
|
|
||||||
Reference to the
|
|
||||||
:class:`~sideshow.db.model.customers.PendingCustomer` record
|
|
||||||
for the order, if applicable.
|
|
||||||
|
|
||||||
See also :attr:`customer_id` and :attr:`local_customer`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
customer_name = sa.Column(
|
customer_name = sa.Column(
|
||||||
sa.String(length=100),
|
sa.String(length=100),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
@ -156,76 +90,13 @@ class Order(model.Base): # pylint: disable=too-few-public-methods
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
created = sa.Column(
|
|
||||||
sa.DateTime(timezone=True),
|
|
||||||
nullable=False,
|
|
||||||
default=datetime.datetime.now,
|
|
||||||
doc="""
|
|
||||||
Timestamp when the order was created.
|
|
||||||
|
|
||||||
If the order is created via New Order Batch, this will match the
|
class OrderItemMixin: # pylint: disable=too-few-public-methods
|
||||||
batch execution timestamp.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
created_by_uuid = model.uuid_fk_column("user.uuid", nullable=False)
|
|
||||||
created_by = orm.relationship(
|
|
||||||
model.User,
|
|
||||||
cascade_backrefs=False,
|
|
||||||
doc="""
|
|
||||||
Reference to the
|
|
||||||
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
|
|
||||||
created the order.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
items = orm.relationship(
|
|
||||||
"OrderItem",
|
|
||||||
collection_class=ordering_list("sequence", count_from=1),
|
|
||||||
cascade="all, delete-orphan",
|
|
||||||
cascade_backrefs=False,
|
|
||||||
back_populates="order",
|
|
||||||
doc="""
|
|
||||||
List of :class:`OrderItem` records belonging to the order.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.order_id)
|
|
||||||
|
|
||||||
|
|
||||||
class OrderItem(model.Base):
|
|
||||||
"""
|
"""
|
||||||
Represents an :term:`order item` within an :class:`Order`.
|
Mixin class providing common columns for order items and new order
|
||||||
|
batch rows.
|
||||||
Usually these are created from
|
|
||||||
:class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow`
|
|
||||||
records.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "sideshow_order_item"
|
|
||||||
|
|
||||||
uuid = model.uuid_column()
|
|
||||||
|
|
||||||
order_uuid = model.uuid_fk_column("sideshow_order.uuid", nullable=False)
|
|
||||||
order = orm.relationship(
|
|
||||||
Order,
|
|
||||||
cascade_backrefs=False,
|
|
||||||
back_populates="items",
|
|
||||||
doc="""
|
|
||||||
Reference to the :class:`Order` to which the item belongs.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
sequence = sa.Column(
|
|
||||||
sa.Integer(),
|
|
||||||
nullable=False,
|
|
||||||
doc="""
|
|
||||||
1-based numeric sequence for the item, i.e. its line number within
|
|
||||||
the order.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
product_id = sa.Column(
|
product_id = sa.Column(
|
||||||
sa.String(length=20),
|
sa.String(length=20),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
@ -237,38 +108,6 @@ class OrderItem(model.Base):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
local_product_uuid = model.uuid_fk_column(
|
|
||||||
"sideshow_product_local.uuid", nullable=True
|
|
||||||
)
|
|
||||||
local_product = orm.relationship(
|
|
||||||
"LocalProduct",
|
|
||||||
cascade_backrefs=False,
|
|
||||||
back_populates="order_items",
|
|
||||||
doc="""
|
|
||||||
Reference to the
|
|
||||||
:class:`~sideshow.db.model.products.LocalProduct` record for
|
|
||||||
the order item, if applicable.
|
|
||||||
|
|
||||||
See also :attr:`product_id` and :attr:`pending_product`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
pending_product_uuid = model.uuid_fk_column(
|
|
||||||
"sideshow_product_pending.uuid", nullable=True
|
|
||||||
)
|
|
||||||
pending_product = orm.relationship(
|
|
||||||
"PendingProduct",
|
|
||||||
cascade_backrefs=False,
|
|
||||||
back_populates="order_items",
|
|
||||||
doc="""
|
|
||||||
Reference to the
|
|
||||||
:class:`~sideshow.db.model.products.PendingProduct` record for
|
|
||||||
the order item, if applicable.
|
|
||||||
|
|
||||||
See also :attr:`product_id` and :attr:`local_product`.
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
|
|
||||||
product_scancode = sa.Column(
|
product_scancode = sa.Column(
|
||||||
sa.String(length=14),
|
sa.String(length=14),
|
||||||
nullable=True,
|
nullable=True,
|
||||||
|
@ -367,6 +206,8 @@ class OrderItem(model.Base):
|
||||||
nullable=True,
|
nullable=True,
|
||||||
doc="""
|
doc="""
|
||||||
Case pack count for the product, if known.
|
Case pack count for the product, if known.
|
||||||
|
|
||||||
|
If this is not set, then customer cannot order a "case" of the item.
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -389,6 +230,10 @@ class OrderItem(model.Base):
|
||||||
|
|
||||||
This should be one of the codes from
|
This should be one of the codes from
|
||||||
:data:`~sideshow.enum.ORDER_UOM`.
|
:data:`~sideshow.enum.ORDER_UOM`.
|
||||||
|
|
||||||
|
Sideshow will treat :data:`~sideshow.enum.ORDER_UOM_CASE`
|
||||||
|
differently but :data:`~sideshow.enum.ORDER_UOM_UNIT` and others
|
||||||
|
are all treated the same (i.e. "unit" is assumed).
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -440,6 +285,8 @@ class OrderItem(model.Base):
|
||||||
This price does *not* reflect the :attr:`discount_percent`. It
|
This price does *not* reflect the :attr:`discount_percent`. It
|
||||||
normally should match either :attr:`unit_price_reg` or
|
normally should match either :attr:`unit_price_reg` or
|
||||||
:attr:`unit_price_sale`.
|
:attr:`unit_price_sale`.
|
||||||
|
|
||||||
|
See also :attr:`case_price_quoted`, if applicable.
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -480,6 +327,181 @@ class OrderItem(model.Base):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Order(OrderMixin, model.Base): # pylint: disable=too-few-public-methods
|
||||||
|
"""
|
||||||
|
Represents an :term:`order` for a customer. Each order has one or
|
||||||
|
more :attr:`items`.
|
||||||
|
|
||||||
|
Usually, orders are created by way of a
|
||||||
|
:class:`~sideshow.db.model.batch.neworder.NewOrderBatch`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "sideshow_order"
|
||||||
|
|
||||||
|
# TODO: this feels a bit hacky yet but it does avoid problems
|
||||||
|
# showing the Orders grid for a PendingCustomer
|
||||||
|
__colanderalchemy_config__ = {
|
||||||
|
"excludes": ["items"],
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
order_id = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
Unique ID for the order.
|
||||||
|
|
||||||
|
When the order is created from New Order Batch, this order ID will
|
||||||
|
match the batch ID.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
store = orm.relationship(
|
||||||
|
"Store",
|
||||||
|
primaryjoin="Store.store_id == Order.store_id",
|
||||||
|
foreign_keys="Order.store_id",
|
||||||
|
doc="""
|
||||||
|
Reference to the :class:`~sideshow.db.model.stores.Store`
|
||||||
|
record, if applicable.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
local_customer_uuid = model.uuid_fk_column(
|
||||||
|
"sideshow_customer_local.uuid", nullable=True
|
||||||
|
)
|
||||||
|
local_customer = orm.relationship(
|
||||||
|
"LocalCustomer",
|
||||||
|
cascade_backrefs=False,
|
||||||
|
back_populates="orders",
|
||||||
|
doc="""
|
||||||
|
Reference to the
|
||||||
|
:class:`~sideshow.db.model.customers.LocalCustomer` record
|
||||||
|
for the order, if applicable.
|
||||||
|
|
||||||
|
See also :attr:`customer_id` and :attr:`pending_customer`.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
pending_customer_uuid = model.uuid_fk_column(
|
||||||
|
"sideshow_customer_pending.uuid", nullable=True
|
||||||
|
)
|
||||||
|
pending_customer = orm.relationship(
|
||||||
|
"PendingCustomer",
|
||||||
|
cascade_backrefs=False,
|
||||||
|
back_populates="orders",
|
||||||
|
doc="""
|
||||||
|
Reference to the
|
||||||
|
:class:`~sideshow.db.model.customers.PendingCustomer` record
|
||||||
|
for the order, if applicable.
|
||||||
|
|
||||||
|
See also :attr:`customer_id` and :attr:`local_customer`.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
created = sa.Column(
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
nullable=False,
|
||||||
|
default=datetime.datetime.now,
|
||||||
|
doc="""
|
||||||
|
Timestamp when the order was created.
|
||||||
|
|
||||||
|
If the order is created via New Order Batch, this will match the
|
||||||
|
batch execution timestamp.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
created_by_uuid = model.uuid_fk_column("user.uuid", nullable=False)
|
||||||
|
created_by = orm.relationship(
|
||||||
|
model.User,
|
||||||
|
cascade_backrefs=False,
|
||||||
|
doc="""
|
||||||
|
Reference to the
|
||||||
|
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
|
||||||
|
created the order.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
items = orm.relationship(
|
||||||
|
"OrderItem",
|
||||||
|
collection_class=ordering_list("sequence", count_from=1),
|
||||||
|
cascade="all, delete-orphan",
|
||||||
|
cascade_backrefs=False,
|
||||||
|
back_populates="order",
|
||||||
|
doc="""
|
||||||
|
List of :class:`OrderItem` records belonging to the order.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.order_id)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItem(OrderItemMixin, model.Base):
|
||||||
|
"""
|
||||||
|
Represents an :term:`order item` within an :class:`Order`.
|
||||||
|
|
||||||
|
Usually these are created from
|
||||||
|
:class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow`
|
||||||
|
records.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__tablename__ = "sideshow_order_item"
|
||||||
|
|
||||||
|
uuid = model.uuid_column()
|
||||||
|
|
||||||
|
order_uuid = model.uuid_fk_column("sideshow_order.uuid", nullable=False)
|
||||||
|
order = orm.relationship(
|
||||||
|
Order,
|
||||||
|
cascade_backrefs=False,
|
||||||
|
back_populates="items",
|
||||||
|
doc="""
|
||||||
|
Reference to the :class:`Order` to which the item belongs.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence = sa.Column(
|
||||||
|
sa.Integer(),
|
||||||
|
nullable=False,
|
||||||
|
doc="""
|
||||||
|
1-based numeric sequence for the item, i.e. its line number within
|
||||||
|
the order.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
local_product_uuid = model.uuid_fk_column(
|
||||||
|
"sideshow_product_local.uuid", nullable=True
|
||||||
|
)
|
||||||
|
local_product = orm.relationship(
|
||||||
|
"LocalProduct",
|
||||||
|
cascade_backrefs=False,
|
||||||
|
back_populates="order_items",
|
||||||
|
doc="""
|
||||||
|
Reference to the
|
||||||
|
:class:`~sideshow.db.model.products.LocalProduct` record for
|
||||||
|
the order item, if applicable.
|
||||||
|
|
||||||
|
See also :attr:`product_id` and :attr:`pending_product`.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
pending_product_uuid = model.uuid_fk_column(
|
||||||
|
"sideshow_product_pending.uuid", nullable=True
|
||||||
|
)
|
||||||
|
pending_product = orm.relationship(
|
||||||
|
"PendingProduct",
|
||||||
|
cascade_backrefs=False,
|
||||||
|
back_populates="order_items",
|
||||||
|
doc="""
|
||||||
|
Reference to the
|
||||||
|
:class:`~sideshow.db.model.products.PendingProduct` record for
|
||||||
|
the order item, if applicable.
|
||||||
|
|
||||||
|
See also :attr:`product_id` and :attr:`local_product`.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
status_code = sa.Column(
|
status_code = sa.Column(
|
||||||
sa.Integer(),
|
sa.Integer(),
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|
102
src/sideshow/web/util.py
Normal file
102
src/sideshow/web/util.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Sideshow -- Case/Special Order Tracker
|
||||||
|
# Copyright © 2024-2025 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Sideshow.
|
||||||
|
#
|
||||||
|
# Sideshow 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.
|
||||||
|
#
|
||||||
|
# Sideshow 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 Sideshow. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Web Utility Functions
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def make_new_order_batches_grid(request, **kwargs):
|
||||||
|
"""
|
||||||
|
Make and return the grid for the New Order Batches field.
|
||||||
|
"""
|
||||||
|
config = request.wutta_config
|
||||||
|
app = config.get_app()
|
||||||
|
model = app.model
|
||||||
|
web = app.get_web_handler()
|
||||||
|
|
||||||
|
if "key" not in kwargs:
|
||||||
|
route_prefix = kwargs.pop("route_prefix")
|
||||||
|
kwargs["key"] = f"{route_prefix}.view.new_order_batches"
|
||||||
|
|
||||||
|
kwargs.setdefault("model_class", model.NewOrderBatch)
|
||||||
|
kwargs.setdefault(
|
||||||
|
"columns",
|
||||||
|
[
|
||||||
|
"id",
|
||||||
|
"total_price",
|
||||||
|
"created",
|
||||||
|
"created_by",
|
||||||
|
"executed",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
kwargs.setdefault("labels", {"id": "Batch ID"})
|
||||||
|
kwargs.setdefault("renderers", {"id": "batch_id", "total_price": "currency"})
|
||||||
|
grid = web.make_grid(request, **kwargs)
|
||||||
|
|
||||||
|
if request.has_perm("neworder_batches.view"):
|
||||||
|
|
||||||
|
def view_url(batch, i): # pylint: disable=unused-argument
|
||||||
|
return request.route_url("neworder_batches.view", uuid=batch.uuid)
|
||||||
|
|
||||||
|
grid.add_action("view", icon="eye", url=view_url)
|
||||||
|
grid.set_link("id")
|
||||||
|
|
||||||
|
return grid
|
||||||
|
|
||||||
|
|
||||||
|
def make_orders_grid(request, **kwargs):
|
||||||
|
"""
|
||||||
|
Make and return the grid for the Orders field.
|
||||||
|
"""
|
||||||
|
config = request.wutta_config
|
||||||
|
app = config.get_app()
|
||||||
|
model = app.model
|
||||||
|
web = app.get_web_handler()
|
||||||
|
|
||||||
|
if "key" not in kwargs:
|
||||||
|
route_prefix = kwargs.pop("route_prefix")
|
||||||
|
kwargs["key"] = f"{route_prefix}.view.orders"
|
||||||
|
|
||||||
|
kwargs.setdefault("model_class", model.Order)
|
||||||
|
kwargs.setdefault(
|
||||||
|
"columns",
|
||||||
|
[
|
||||||
|
"order_id",
|
||||||
|
"total_price",
|
||||||
|
"created",
|
||||||
|
"created_by",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
kwargs.setdefault("labels", {"order_id": "Order ID"})
|
||||||
|
kwargs.setdefault("renderers", {"total_price": "currency"})
|
||||||
|
grid = web.make_grid(request, **kwargs)
|
||||||
|
|
||||||
|
if request.has_perm("orders.view"):
|
||||||
|
|
||||||
|
def view_url(order, i): # pylint: disable=unused-argument
|
||||||
|
return request.route_url("orders.view", uuid=order.uuid)
|
||||||
|
|
||||||
|
grid.add_action("view", icon="eye", url=view_url)
|
||||||
|
grid.set_link("order_id")
|
||||||
|
|
||||||
|
return grid
|
|
@ -25,9 +25,11 @@ Views for Customers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from wuttaweb.views import MasterView
|
from wuttaweb.views import MasterView
|
||||||
from wuttaweb.forms.schema import UserRef, WuttaEnum
|
from wuttaweb.forms.schema import WuttaEnum
|
||||||
|
|
||||||
from sideshow.db.model import LocalCustomer, PendingCustomer
|
from sideshow.db.model import LocalCustomer, PendingCustomer
|
||||||
|
from sideshow.web.views.shared import PendingMixin
|
||||||
|
from sideshow.web.util import make_new_order_batches_grid, make_orders_grid
|
||||||
|
|
||||||
|
|
||||||
class LocalCustomerView(MasterView): # pylint: disable=abstract-method
|
class LocalCustomerView(MasterView): # pylint: disable=abstract-method
|
||||||
|
@ -120,72 +122,20 @@ class LocalCustomerView(MasterView): # pylint: disable=abstract-method
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the Orders field.
|
Make and return the grid for the Orders field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
return make_orders_grid(
|
||||||
route_prefix = self.get_route_prefix()
|
self.request, route_prefix=self.get_route_prefix(), data=customer.orders
|
||||||
|
|
||||||
grid = self.make_grid(
|
|
||||||
key=f"{route_prefix}.view.orders",
|
|
||||||
model_class=model.Order,
|
|
||||||
data=customer.orders,
|
|
||||||
columns=[
|
|
||||||
"order_id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"order_id": "Order ID",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
grid.set_renderer("total_price", grid.render_currency)
|
|
||||||
|
|
||||||
if self.request.has_perm("orders.view"):
|
|
||||||
|
|
||||||
def view_url(order, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("orders.view", uuid=order.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("order_id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def make_new_order_batches_grid(self, customer):
|
def make_new_order_batches_grid(self, customer):
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the New Order Batches field.
|
Make and return the grid for the New Order Batches field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
return make_new_order_batches_grid(
|
||||||
route_prefix = self.get_route_prefix()
|
self.request,
|
||||||
|
route_prefix=self.get_route_prefix(),
|
||||||
grid = self.make_grid(
|
|
||||||
key=f"{route_prefix}.view.new_order_batches",
|
|
||||||
model_class=model.NewOrderBatch,
|
|
||||||
data=customer.new_order_batches,
|
data=customer.new_order_batches,
|
||||||
columns=[
|
|
||||||
"id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
"executed",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"id": "Batch ID",
|
|
||||||
},
|
|
||||||
renderers={
|
|
||||||
"id": "batch_id",
|
|
||||||
"total_price": "currency",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.has_perm("neworder_batches.view"):
|
|
||||||
|
|
||||||
def view_url(batch, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("neworder_batches.view", uuid=batch.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def objectify(self, form): # pylint: disable=empty-docstring
|
def objectify(self, form): # pylint: disable=empty-docstring
|
||||||
""" """
|
""" """
|
||||||
customer = super().objectify(form)
|
customer = super().objectify(form)
|
||||||
|
@ -197,7 +147,7 @@ class LocalCustomerView(MasterView): # pylint: disable=abstract-method
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
|
|
||||||
class PendingCustomerView(MasterView): # pylint: disable=abstract-method
|
class PendingCustomerView(PendingMixin, MasterView): # pylint: disable=abstract-method
|
||||||
"""
|
"""
|
||||||
Master view for
|
Master view for
|
||||||
:class:`~sideshow.db.model.customers.PendingCustomer`; route
|
:class:`~sideshow.db.model.customers.PendingCustomer`; route
|
||||||
|
@ -270,7 +220,8 @@ class PendingCustomerView(MasterView): # pylint: disable=abstract-method
|
||||||
f = form
|
f = form
|
||||||
super().configure_form(f)
|
super().configure_form(f)
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
customer = f.model_instance
|
|
||||||
|
self.configure_form_pending(f)
|
||||||
|
|
||||||
# customer_id
|
# customer_id
|
||||||
if self.creating:
|
if self.creating:
|
||||||
|
@ -285,101 +236,24 @@ class PendingCustomerView(MasterView): # pylint: disable=abstract-method
|
||||||
f.set_node("status", WuttaEnum(self.request, enum.PendingCustomerStatus))
|
f.set_node("status", WuttaEnum(self.request, enum.PendingCustomerStatus))
|
||||||
f.set_readonly("status")
|
f.set_readonly("status")
|
||||||
|
|
||||||
# created
|
|
||||||
if self.creating:
|
|
||||||
f.remove("created")
|
|
||||||
else:
|
|
||||||
f.set_readonly("created")
|
|
||||||
|
|
||||||
# created_by
|
|
||||||
if self.creating:
|
|
||||||
f.remove("created_by")
|
|
||||||
else:
|
|
||||||
f.set_node("created_by", UserRef(self.request))
|
|
||||||
f.set_readonly("created_by")
|
|
||||||
|
|
||||||
# orders
|
|
||||||
if self.creating or self.editing:
|
|
||||||
f.remove("orders")
|
|
||||||
else:
|
|
||||||
f.set_grid("orders", self.make_orders_grid(customer))
|
|
||||||
|
|
||||||
# new_order_batches
|
|
||||||
if self.creating or self.editing:
|
|
||||||
f.remove("new_order_batches")
|
|
||||||
else:
|
|
||||||
f.set_grid("new_order_batches", self.make_new_order_batches_grid(customer))
|
|
||||||
|
|
||||||
def make_orders_grid(self, customer):
|
def make_orders_grid(self, customer):
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the Orders field.
|
Make and return the grid for the Orders field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
return make_orders_grid(
|
||||||
route_prefix = self.get_route_prefix()
|
self.request, route_prefix=self.get_route_prefix(), data=customer.orders
|
||||||
|
|
||||||
grid = self.make_grid(
|
|
||||||
key=f"{route_prefix}.view.orders",
|
|
||||||
model_class=model.Order,
|
|
||||||
data=customer.orders,
|
|
||||||
columns=[
|
|
||||||
"order_id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"order_id": "Order ID",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
grid.set_renderer("total_price", grid.render_currency)
|
|
||||||
|
|
||||||
if self.request.has_perm("orders.view"):
|
|
||||||
|
|
||||||
def view_url(order, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("orders.view", uuid=order.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("order_id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def make_new_order_batches_grid(self, customer):
|
def make_new_order_batches_grid(self, customer):
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the New Order Batches field.
|
Make and return the grid for the New Order Batches field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
return make_new_order_batches_grid(
|
||||||
route_prefix = self.get_route_prefix()
|
self.request,
|
||||||
|
route_prefix=self.get_route_prefix(),
|
||||||
grid = self.make_grid(
|
|
||||||
key=f"{route_prefix}.view.new_order_batches",
|
|
||||||
model_class=model.NewOrderBatch,
|
|
||||||
data=customer.new_order_batches,
|
data=customer.new_order_batches,
|
||||||
columns=[
|
|
||||||
"id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
"executed",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"id": "Batch ID",
|
|
||||||
},
|
|
||||||
renderers={
|
|
||||||
"id": "batch_id",
|
|
||||||
"total_price": "currency",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.has_perm("neworder_batches.view"):
|
|
||||||
|
|
||||||
def view_url(batch, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("neworder_batches.view", uuid=batch.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def objectify(self, form): # pylint: disable=empty-docstring
|
def objectify(self, form): # pylint: disable=empty-docstring
|
||||||
""" """
|
""" """
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
|
|
|
@ -25,10 +25,12 @@ Views for Products
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from wuttaweb.views import MasterView
|
from wuttaweb.views import MasterView
|
||||||
from wuttaweb.forms.schema import UserRef, WuttaMoney, WuttaQuantity
|
from wuttaweb.forms.schema import WuttaMoney, WuttaQuantity
|
||||||
|
|
||||||
from sideshow.enum import PendingProductStatus
|
from sideshow.enum import PendingProductStatus
|
||||||
from sideshow.db.model import LocalProduct, PendingProduct
|
from sideshow.db.model import LocalProduct, PendingProduct
|
||||||
|
from sideshow.web.views.shared import PendingMixin
|
||||||
|
from sideshow.web.util import make_new_order_batches_grid, make_orders_grid
|
||||||
|
|
||||||
|
|
||||||
class LocalProductView(MasterView): # pylint: disable=abstract-method
|
class LocalProductView(MasterView): # pylint: disable=abstract-method
|
||||||
|
@ -153,82 +155,28 @@ class LocalProductView(MasterView): # pylint: disable=abstract-method
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the Orders field.
|
Make and return the grid for the Orders field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
|
||||||
route_prefix = self.get_route_prefix()
|
|
||||||
|
|
||||||
orders = {item.order for item in product.order_items}
|
orders = {item.order for item in product.order_items}
|
||||||
orders = sorted(orders, key=lambda order: order.order_id)
|
orders = sorted(orders, key=lambda order: order.order_id)
|
||||||
|
|
||||||
grid = self.make_grid(
|
return make_orders_grid(
|
||||||
key=f"{route_prefix}.view.orders",
|
self.request, route_prefix=self.get_route_prefix(), data=orders
|
||||||
model_class=model.Order,
|
|
||||||
data=orders,
|
|
||||||
columns=[
|
|
||||||
"order_id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"order_id": "Order ID",
|
|
||||||
},
|
|
||||||
renderers={
|
|
||||||
"total_price": "currency",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.has_perm("orders.view"):
|
|
||||||
|
|
||||||
def view_url(order, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("orders.view", uuid=order.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("order_id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def make_new_order_batches_grid(self, product):
|
def make_new_order_batches_grid(self, product):
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the New Order Batches field.
|
Make and return the grid for the New Order Batches field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
|
||||||
route_prefix = self.get_route_prefix()
|
|
||||||
|
|
||||||
batches = {row.batch for row in product.new_order_batch_rows}
|
batches = {row.batch for row in product.new_order_batch_rows}
|
||||||
batches = sorted(batches, key=lambda batch: batch.id)
|
batches = sorted(batches, key=lambda batch: batch.id)
|
||||||
|
|
||||||
grid = self.make_grid(
|
return make_new_order_batches_grid(
|
||||||
key=f"{route_prefix}.view.new_order_batches",
|
self.request,
|
||||||
model_class=model.NewOrderBatch,
|
route_prefix=self.get_route_prefix(),
|
||||||
data=batches,
|
data=batches,
|
||||||
columns=[
|
|
||||||
"id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
"executed",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"id": "Batch ID",
|
|
||||||
"status_code": "Status",
|
|
||||||
},
|
|
||||||
renderers={
|
|
||||||
"id": "batch_id",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.has_perm("neworder_batches.view"):
|
|
||||||
|
|
||||||
def view_url(batch, i): # pylint: disable=unused-argument
|
class PendingProductView(PendingMixin, MasterView): # pylint: disable=abstract-method
|
||||||
return self.request.route_url("neworder_batches.view", uuid=batch.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
|
|
||||||
class PendingProductView(MasterView): # pylint: disable=abstract-method
|
|
||||||
"""
|
"""
|
||||||
Master view for
|
Master view for
|
||||||
:class:`~sideshow.db.model.products.PendingProduct`; route
|
:class:`~sideshow.db.model.products.PendingProduct`; route
|
||||||
|
@ -330,7 +278,8 @@ class PendingProductView(MasterView): # pylint: disable=abstract-method
|
||||||
""" """
|
""" """
|
||||||
f = form
|
f = form
|
||||||
super().configure_form(f)
|
super().configure_form(f)
|
||||||
product = f.model_instance
|
|
||||||
|
self.configure_form_pending(f)
|
||||||
|
|
||||||
# product_id
|
# product_id
|
||||||
if self.creating:
|
if self.creating:
|
||||||
|
@ -344,109 +293,30 @@ class PendingProductView(MasterView): # pylint: disable=abstract-method
|
||||||
# notes
|
# notes
|
||||||
f.set_widget("notes", "notes")
|
f.set_widget("notes", "notes")
|
||||||
|
|
||||||
# created
|
|
||||||
if self.creating:
|
|
||||||
f.remove("created")
|
|
||||||
else:
|
|
||||||
f.set_readonly("created")
|
|
||||||
|
|
||||||
# created_by
|
|
||||||
if self.creating:
|
|
||||||
f.remove("created_by")
|
|
||||||
else:
|
|
||||||
f.set_node("created_by", UserRef(self.request))
|
|
||||||
f.set_readonly("created_by")
|
|
||||||
|
|
||||||
# orders
|
|
||||||
if self.creating or self.editing:
|
|
||||||
f.remove("orders")
|
|
||||||
else:
|
|
||||||
f.set_grid("orders", self.make_orders_grid(product))
|
|
||||||
|
|
||||||
# new_order_batches
|
|
||||||
if self.creating or self.editing:
|
|
||||||
f.remove("new_order_batches")
|
|
||||||
else:
|
|
||||||
f.set_grid("new_order_batches", self.make_new_order_batches_grid(product))
|
|
||||||
|
|
||||||
def make_orders_grid(self, product):
|
def make_orders_grid(self, product):
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the Orders field.
|
Make and return the grid for the Orders field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
|
||||||
route_prefix = self.get_route_prefix()
|
|
||||||
|
|
||||||
orders = {item.order for item in product.order_items}
|
orders = {item.order for item in product.order_items}
|
||||||
orders = sorted(orders, key=lambda order: order.order_id)
|
orders = sorted(orders, key=lambda order: order.order_id)
|
||||||
|
|
||||||
grid = self.make_grid(
|
return make_orders_grid(
|
||||||
key=f"{route_prefix}.view.orders",
|
self.request, route_prefix=self.get_route_prefix(), data=orders
|
||||||
model_class=model.Order,
|
|
||||||
data=orders,
|
|
||||||
columns=[
|
|
||||||
"order_id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"order_id": "Order ID",
|
|
||||||
},
|
|
||||||
renderers={
|
|
||||||
"total_price": "currency",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.has_perm("orders.view"):
|
|
||||||
|
|
||||||
def view_url(order, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("orders.view", uuid=order.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("order_id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def make_new_order_batches_grid(self, product):
|
def make_new_order_batches_grid(self, product):
|
||||||
"""
|
"""
|
||||||
Make and return the grid for the New Order Batches field.
|
Make and return the grid for the New Order Batches field.
|
||||||
"""
|
"""
|
||||||
model = self.app.model
|
|
||||||
route_prefix = self.get_route_prefix()
|
|
||||||
|
|
||||||
batches = {row.batch for row in product.new_order_batch_rows}
|
batches = {row.batch for row in product.new_order_batch_rows}
|
||||||
batches = sorted(batches, key=lambda batch: batch.id)
|
batches = sorted(batches, key=lambda batch: batch.id)
|
||||||
|
|
||||||
grid = self.make_grid(
|
return make_new_order_batches_grid(
|
||||||
key=f"{route_prefix}.view.new_order_batches",
|
self.request,
|
||||||
model_class=model.NewOrderBatch,
|
route_prefix=self.get_route_prefix(),
|
||||||
data=batches,
|
data=batches,
|
||||||
columns=[
|
|
||||||
"id",
|
|
||||||
"total_price",
|
|
||||||
"created",
|
|
||||||
"created_by",
|
|
||||||
"executed",
|
|
||||||
],
|
|
||||||
labels={
|
|
||||||
"id": "Batch ID",
|
|
||||||
"status_code": "Status",
|
|
||||||
},
|
|
||||||
renderers={
|
|
||||||
"id": "batch_id",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.request.has_perm("neworder_batches.view"):
|
|
||||||
|
|
||||||
def view_url(batch, i): # pylint: disable=unused-argument
|
|
||||||
return self.request.route_url("neworder_batches.view", uuid=batch.uuid)
|
|
||||||
|
|
||||||
grid.add_action("view", icon="eye", url=view_url)
|
|
||||||
grid.set_link("id")
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def get_template_context(self, context): # pylint: disable=empty-docstring
|
def get_template_context(self, context): # pylint: disable=empty-docstring
|
||||||
""" """
|
""" """
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
|
|
64
src/sideshow/web/views/shared.py
Normal file
64
src/sideshow/web/views/shared.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Sideshow -- Case/Special Order Tracker
|
||||||
|
# Copyright © 2024-2025 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Sideshow.
|
||||||
|
#
|
||||||
|
# Sideshow 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.
|
||||||
|
#
|
||||||
|
# Sideshow 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 Sideshow. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Shared View Logic
|
||||||
|
"""
|
||||||
|
|
||||||
|
from wuttaweb.forms.schema import UserRef
|
||||||
|
|
||||||
|
|
||||||
|
class PendingMixin: # pylint: disable=too-few-public-methods
|
||||||
|
"""
|
||||||
|
Mixin class with logic shared by Pending Customer and Pending
|
||||||
|
Product views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def configure_form_pending(self, form): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
f = form
|
||||||
|
obj = f.model_instance
|
||||||
|
|
||||||
|
# created
|
||||||
|
if self.creating:
|
||||||
|
f.remove("created")
|
||||||
|
else:
|
||||||
|
f.set_readonly("created")
|
||||||
|
|
||||||
|
# created_by
|
||||||
|
if self.creating:
|
||||||
|
f.remove("created_by")
|
||||||
|
else:
|
||||||
|
f.set_node("created_by", UserRef(self.request))
|
||||||
|
f.set_readonly("created_by")
|
||||||
|
|
||||||
|
# orders
|
||||||
|
if self.creating or self.editing:
|
||||||
|
f.remove("orders")
|
||||||
|
else:
|
||||||
|
f.set_grid("orders", self.make_orders_grid(obj))
|
||||||
|
|
||||||
|
# new_order_batches
|
||||||
|
if self.creating or self.editing:
|
||||||
|
f.remove("new_order_batches")
|
||||||
|
else:
|
||||||
|
f.set_grid("new_order_batches", self.make_new_order_batches_grid(obj))
|
Loading…
Add table
Add a link
Reference in a new issue