diff --git a/CHANGELOG.md b/CHANGELOG.md index 3945002..04e0a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,3 @@ -## v0.4.0 (2025-01-23) - -### Feat - -- add initial workflow master views, UI features -- add tools to change order item status; add notes -- add initial support for order item events - -### Fix - -- customize "view order item" page w/ panels -- add loading overlay for expensive calls in orders/create -- hide local customer when not applicable, for order view - ## v0.3.0 (2025-01-13) ### Feat diff --git a/docs/api/sideshow.orders.rst b/docs/api/sideshow.orders.rst deleted file mode 100644 index fd1850e..0000000 --- a/docs/api/sideshow.orders.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``sideshow.orders`` -=================== - -.. automodule:: sideshow.orders - :members: diff --git a/docs/glossary.rst b/docs/glossary.rst index 647faa7..0fec4c0 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -45,27 +45,12 @@ Glossary "submits" the order, the batch is executed which creates a true :term:`order`. - The batch handler is responsible for business logic for the order - creation step; the :term:`order handler` is responsible for - everything thereafter. - - :class:`~sideshow.batch.neworder.NewOrderBatchHandler` is the - default handler for this. - order This is the central focus of the app; it refers to a customer case/special order which is tracked over time, from placement to fulfillment. Each order may have one or more :term:`order items `. - order handler - The :term:`handler` responsible for business logic surrounding - :term:`order` workflows *after* initial creation. (Whereas the - :term:`new order batch` handler is responsible for creation.) - - :class:`~sideshow.orders.OrderHandler` is the default handler for - this. - order item This is effectively a "line item" within an :term:`order`. It represents a particular product, with quantity and pricing diff --git a/docs/index.rst b/docs/index.rst index 3bc3a5f..e1673e3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,7 +39,6 @@ the narrative docs are pretty scant. That will eventually change. api/sideshow.db.model.orders api/sideshow.db.model.products api/sideshow.enum - api/sideshow.orders api/sideshow.web api/sideshow.web.app api/sideshow.web.forms diff --git a/pyproject.toml b/pyproject.toml index 5b65704..15d4539 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "Sideshow" -version = "0.4.0" +version = "0.3.0" description = "Case/Special Order Tracker" readme = "README.md" authors = [ @@ -33,7 +33,7 @@ license = {text = "GNU General Public License v3+"} requires-python = ">= 3.8" dependencies = [ "psycopg2", - "WuttaWeb>=0.20.5", + "WuttaWeb>=0.20.1", ] [project.optional-dependencies] diff --git a/src/sideshow/batch/neworder.py b/src/sideshow/batch/neworder.py index 6295407..e2ce3a4 100644 --- a/src/sideshow/batch/neworder.py +++ b/src/sideshow/batch/neworder.py @@ -44,9 +44,6 @@ class NewOrderBatchHandler(BatchHandler): :class:`~sideshow.db.model.batch.neworder.NewOrderBatch` tracks all user input until they "submit" (execute) at which point an :class:`~sideshow.db.model.orders.Order` is created. - - After the batch has executed the :term:`order handler` takes over - responsibility for the rest of the order lifecycle. """ model_class = NewOrderBatch @@ -994,35 +991,9 @@ class NewOrderBatchHandler(BatchHandler): order.items.append(item) # set item status - self.set_initial_item_status(item, user) + item.status_code = enum.ORDER_ITEM_STATUS_INITIATED self.app.progress_loop(convert, rows, progress, message="Converting batch rows to order items") session.flush() return order - - def set_initial_item_status(self, item, user, **kwargs): - """ - Set the initial status and attach event(s) for the given item. - - This is called from :meth:`make_new_order()` for each item - after it is added to the order. - - Default logic will set status to - :data:`~sideshow.enum.ORDER_ITEM_STATUS_READY` and attach 2 - events: - - * :data:`~sideshow.enum.ORDER_ITEM_EVENT_INITIATED` - * :data:`~sideshow.enum.ORDER_ITEM_EVENT_READY` - - :param item: :class:`~sideshow.db.model.orders.OrderItem` - being added to the new order. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who - is performing the action. - """ - enum = self.app.enum - item.add_event(enum.ORDER_ITEM_EVENT_INITIATED, user) - item.add_event(enum.ORDER_ITEM_EVENT_READY, user) - item.status_code = enum.ORDER_ITEM_STATUS_READY diff --git a/src/sideshow/db/alembic/versions/7a6df83afbd4_initial_order_tables.py b/src/sideshow/db/alembic/versions/7a6df83afbd4_initial_order_tables.py index 6a0a589..be6eee8 100644 --- a/src/sideshow/db/alembic/versions/7a6df83afbd4_initial_order_tables.py +++ b/src/sideshow/db/alembic/versions/7a6df83afbd4_initial_order_tables.py @@ -156,19 +156,6 @@ def upgrade() -> None: sa.PrimaryKeyConstraint('uuid', name=op.f('pk_order_item')) ) - # sideshow_order_item_event - op.create_table('sideshow_order_item_event', - sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('item_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('type_code', sa.Integer(), nullable=False), - sa.Column('occurred', sa.DateTime(timezone=True), nullable=False), - sa.Column('actor_uuid', wuttjamaican.db.util.UUID(), nullable=False), - sa.Column('note', sa.Text(), nullable=True), - sa.ForeignKeyConstraint(['actor_uuid'], ['user.uuid'], name=op.f('fk_sideshow_order_item_event_actor_uuid_user')), - sa.ForeignKeyConstraint(['item_uuid'], ['sideshow_order_item.uuid'], name=op.f('fk_sideshow_order_item_event_item_uuid_sideshow_order_item')), - sa.PrimaryKeyConstraint('uuid', name=op.f('pk_sideshow_order_item_event')) - ) - # sideshow_batch_neworder op.create_table('sideshow_batch_neworder', sa.Column('uuid', wuttjamaican.db.util.UUID(), nullable=False), @@ -240,9 +227,6 @@ def downgrade() -> None: op.drop_table('sideshow_batch_neworder_row') op.drop_table('sideshow_batch_neworder') - # sideshow_order_item_event - op.drop_table('sideshow_order_item_event') - # sideshow_order_item op.drop_table('sideshow_order_item') diff --git a/src/sideshow/db/model/__init__.py b/src/sideshow/db/model/__init__.py index f53dd27..28d09f3 100644 --- a/src/sideshow/db/model/__init__.py +++ b/src/sideshow/db/model/__init__.py @@ -32,7 +32,6 @@ Primary :term:`data models `: * :class:`~sideshow.db.model.orders.Order` * :class:`~sideshow.db.model.orders.OrderItem` -* :class:`~sideshow.db.model.orders.OrderItemEvent` * :class:`~sideshow.db.model.customers.LocalCustomer` * :class:`~sideshow.db.model.products.LocalProduct` * :class:`~sideshow.db.model.customers.PendingCustomer` @@ -50,7 +49,7 @@ from wuttjamaican.db.model import * # sideshow models from .customers import LocalCustomer, PendingCustomer from .products import LocalProduct, PendingProduct -from .orders import Order, OrderItem, OrderItemEvent +from .orders import Order, OrderItem # batch models from .batch.neworder import NewOrderBatch, NewOrderBatchRow diff --git a/src/sideshow/db/model/orders.py b/src/sideshow/db/model/orders.py index 2cadeaa..f694028 100644 --- a/src/sideshow/db/model/orders.py +++ b/src/sideshow/db/model/orders.py @@ -2,7 +2,7 @@ ################################################################################ # # Sideshow -- Case/Special Order Tracker -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Sideshow. # @@ -332,16 +332,6 @@ class OrderItem(model.Base): applicable/known. """) - events = orm.relationship( - 'OrderItemEvent', - order_by='OrderItemEvent.occurred, OrderItemEvent.uuid', - cascade='all, delete-orphan', - cascade_backrefs=False, - back_populates='item', - doc=""" - List of :class:`OrderItemEvent` records for the item. - """) - @property def full_description(self): """ """ @@ -354,52 +344,3 @@ class OrderItem(model.Base): def __str__(self): return self.full_description - - def add_event(self, type_code, user, **kwargs): - """ - Convenience method to add a new :class:`OrderItemEvent` for - the item. - """ - kwargs['type_code'] = type_code - kwargs['actor'] = user - self.events.append(OrderItemEvent(**kwargs)) - - -class OrderItemEvent(model.Base): - """ - An event in the life of an :term:`order item`. - """ - __tablename__ = 'sideshow_order_item_event' - - uuid = model.uuid_column() - - item_uuid = model.uuid_fk_column('sideshow_order_item.uuid', nullable=False) - item = orm.relationship( - OrderItem, - cascade_backrefs=False, - back_populates='events', - doc=""" - Reference to the :class:`OrderItem` to which the event - pertains. - """) - - type_code = sa.Column(sa.Integer, nullable=False, doc=""" - Code indicating the type of event; values must be defined in - :data:`~sideshow.enum.ORDER_ITEM_EVENT`. - """) - - occurred = sa.Column(sa.DateTime(timezone=True), nullable=False, default=datetime.datetime.now, doc=""" - Date and time when the event occurred. - """) - - actor_uuid = model.uuid_fk_column('user.uuid', nullable=False) - actor = orm.relationship( - model.User, - doc=""" - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who - performed the action. - """) - - note = sa.Column(sa.Text(), nullable=True, doc=""" - Optional note recorded for the event. - """) diff --git a/src/sideshow/enum.py b/src/sideshow/enum.py index a761d02..2bd1e1a 100644 --- a/src/sideshow/enum.py +++ b/src/sideshow/enum.py @@ -2,7 +2,7 @@ ################################################################################ # # Sideshow -- Case/Special Order Tracker -# Copyright © 2024-2025 Lance Edgar +# Copyright © 2024 Lance Edgar # # This file is part of Sideshow. # @@ -108,101 +108,23 @@ class PendingProductStatus(Enum): ######################################## ORDER_ITEM_STATUS_UNINITIATED = 1 -""" -Indicates the item is "not yet initiated" - this probably is not -useful but exists as a possibility just in case. -""" - ORDER_ITEM_STATUS_INITIATED = 10 -""" -Indicates the item is "initiated" (aka. created) but not yet "ready" -for buyer/PO. This may imply the price needs confirmation etc. -""" - ORDER_ITEM_STATUS_PAID_BEFORE = 50 -""" -Indicates the customer has fully paid for the item, up-front before -the buyer places PO etc. It implies the item is not yet "ready" for -some reason. -""" - # TODO: deprecate / remove this one ORDER_ITEM_STATUS_PAID = ORDER_ITEM_STATUS_PAID_BEFORE - ORDER_ITEM_STATUS_READY = 100 -""" -Indicates the item is "ready" for buyer to include it on a vendor -purchase order. -""" - ORDER_ITEM_STATUS_PLACED = 200 -""" -Indicates the buyer has placed a vendor purchase order which includes -this item. The item is thereby "on order" until the truck arrives. -""" - ORDER_ITEM_STATUS_RECEIVED = 300 -""" -Indicates the item has been received as part of a vendor delivery. -The item is thereby "on hand" until customer comes in for pickup. -""" - ORDER_ITEM_STATUS_CONTACTED = 350 -""" -Indicates the customer has been notified that the item is "on hand" -and awaiting their pickup. -""" - ORDER_ITEM_STATUS_CONTACT_FAILED = 375 -""" -Indicates the attempt(s) to notify customer have failed. The item is -on hand but the customer does not know to pickup. -""" - ORDER_ITEM_STATUS_DELIVERED = 500 -""" -Indicates the customer has picked up the item. -""" - ORDER_ITEM_STATUS_PAID_AFTER = 550 -""" -Indicates the customer has fully paid for the item, as part of their -pickup. This completes the cycle for orders which require payment on -the tail end. -""" - ORDER_ITEM_STATUS_CANCELED = 900 -""" -Indicates the order item has been canceled. -""" - ORDER_ITEM_STATUS_REFUND_PENDING = 910 -""" -Indicates the order item has been canceled, and the customer is due a -(pending) refund. -""" - ORDER_ITEM_STATUS_REFUNDED = 920 -""" -Indicates the order item has been canceled, and the customer has been -given a refund. -""" - ORDER_ITEM_STATUS_RESTOCKED = 930 -""" -Indicates the product has been restocked, e.g. after the order item -was canceled. -""" - ORDER_ITEM_STATUS_EXPIRED = 940 -""" -Indicates the order item and/or product has expired. -""" - ORDER_ITEM_STATUS_INACTIVE = 950 -""" -Indicates the order item has become inactive. -""" ORDER_ITEM_STATUS = OrderedDict([ (ORDER_ITEM_STATUS_UNINITIATED, "uninitiated"), @@ -222,176 +144,3 @@ ORDER_ITEM_STATUS = OrderedDict([ (ORDER_ITEM_STATUS_EXPIRED, "expired"), (ORDER_ITEM_STATUS_INACTIVE, "inactive"), ]) -""" -Dict of possible code -> label options for :term:`order item` status. - -These codes are referenced by: - -* :attr:`sideshow.db.model.orders.OrderItem.status_code` -""" - - -######################################## -# Order Item Event Type -######################################## - -ORDER_ITEM_EVENT_INITIATED = 10 -""" -Indicates the item was "initiated" - this occurs when the -:term:`order` is first created. -""" - -ORDER_ITEM_EVENT_PRICE_CONFIRMED = 20 -""" -Indicates the item's price was confirmed by a user who is authorized -to do that. -""" - -ORDER_ITEM_EVENT_PAYMENT_RECEIVED = 50 -""" -Indicates payment was received for the item. This may occur toward -the beginning, or toward the end, of the item's life cycle depending -on app configuration etc. -""" - -# TODO: deprecate / remove this -ORDER_ITEM_EVENT_PAID = ORDER_ITEM_EVENT_PAYMENT_RECEIVED - -ORDER_ITEM_EVENT_READY = 100 -""" -Indicates the item has become "ready" for buyer placement on a new -vendor purchase order. Often this will occur when the :term:`order` -is first created, if the data is suitable. However this may be -delayed if e.g. the price needs confirmation. -""" - -ORDER_ITEM_EVENT_CUSTOMER_RESOLVED = 120 -""" -Indicates the customer for the :term:`order` has been assigned to a -"proper" (existing) account. This may happen (after the fact) if the -order was first created with a new/unknown customer. -""" - -ORDER_ITEM_EVENT_PRODUCT_RESOLVED = 140 -""" -Indicates the product for the :term:`order item` has been assigned to -a "proper" (existing) product record. This may happen (after the -fact) if the order was first created with a new/unknown product. -""" - -ORDER_ITEM_EVENT_PLACED = 200 -""" -Indicates the buyer has placed a vendor purchase order which includes -this item. So the item is "on order" until the truck arrives. -""" - -ORDER_ITEM_EVENT_REORDER = 210 -""" -Indicates the item was not received with the delivery on which it was -expected, and must be re-ordered from vendor. -""" - -ORDER_ITEM_EVENT_RECEIVED = 300 -""" -Indicates the receiver has found the item while receiving a vendor -delivery. The item is set aside and is "on hand" until customer comes -in to pick it up. -""" - -ORDER_ITEM_EVENT_CONTACTED = 350 -""" -Indicates the customer has been contacted, to notify them of the item -being on hand and ready for pickup. -""" - -ORDER_ITEM_EVENT_CONTACT_FAILED = 375 -""" -Indicates an attempt was made to contact the customer, to notify them -of item being on hand, but the attempt failed, e.g. due to bad phone -or email on file. -""" - -ORDER_ITEM_EVENT_DELIVERED = 500 -""" -Indicates the customer has picked up the item. -""" - -ORDER_ITEM_EVENT_STATUS_CHANGE = 700 -""" -Indicates a manual status change was made. Such an event should -ideally contain a note with further explanation. -""" - -ORDER_ITEM_EVENT_NOTE_ADDED = 750 -""" -Indicates an arbitrary note was added. -""" - -ORDER_ITEM_EVENT_CANCELED = 900 -""" -Indicates the :term:`order item` was canceled. -""" - -ORDER_ITEM_EVENT_REFUND_PENDING = 910 -""" -Indicates the customer is due a (pending) refund for the item. -""" - -ORDER_ITEM_EVENT_REFUNDED = 920 -""" -Indicates the customer has been refunded for the item. -""" - -ORDER_ITEM_EVENT_RESTOCKED = 930 -""" -Indicates the product has been restocked, e.g. due to the order item -being canceled. -""" - -ORDER_ITEM_EVENT_EXPIRED = 940 -""" -Indicates the order item (or its product) has expired. -""" - -ORDER_ITEM_EVENT_INACTIVE = 950 -""" -Indicates the order item has become inactive. -""" - -ORDER_ITEM_EVENT_OTHER = 999 -""" -Arbitrary event type which does not signify anything in particular. -If used, the event should be given an explanatory note. -""" - -ORDER_ITEM_EVENT = OrderedDict([ - (ORDER_ITEM_EVENT_INITIATED, "initiated"), - (ORDER_ITEM_EVENT_PRICE_CONFIRMED, "price confirmed"), - (ORDER_ITEM_EVENT_PAYMENT_RECEIVED, "payment received"), - (ORDER_ITEM_EVENT_READY, "ready to proceed"), - (ORDER_ITEM_EVENT_CUSTOMER_RESOLVED, "customer resolved"), - (ORDER_ITEM_EVENT_PRODUCT_RESOLVED, "product resolved"), - (ORDER_ITEM_EVENT_PLACED, "placed with vendor"), - (ORDER_ITEM_EVENT_REORDER, "marked for re-order"), - (ORDER_ITEM_EVENT_RECEIVED, "received from vendor"), - (ORDER_ITEM_EVENT_CONTACTED, "customer contacted"), - (ORDER_ITEM_EVENT_CONTACT_FAILED, "contact failed"), - (ORDER_ITEM_EVENT_DELIVERED, "delivered"), - (ORDER_ITEM_EVENT_STATUS_CHANGE, "changed status"), - (ORDER_ITEM_EVENT_NOTE_ADDED, "added note"), - (ORDER_ITEM_EVENT_CANCELED, "canceled"), - (ORDER_ITEM_EVENT_REFUND_PENDING, "refund pending"), - (ORDER_ITEM_EVENT_REFUNDED, "refunded"), - (ORDER_ITEM_EVENT_RESTOCKED, "restocked"), - (ORDER_ITEM_EVENT_EXPIRED, "expired"), - (ORDER_ITEM_EVENT_INACTIVE, "inactive"), - (ORDER_ITEM_EVENT_OTHER, "other"), -]) -""" -Dict of possible code -> label options for :term:`order item` event -types. - -These codes are referenced by: - -* :attr:`sideshow.db.model.orders.OrderItemEvent.type_code` -""" diff --git a/src/sideshow/orders.py b/src/sideshow/orders.py deleted file mode 100644 index 9f99e53..0000000 --- a/src/sideshow/orders.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- 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 . -# -################################################################################ -""" -Sideshow Order Handler -""" - -from wuttjamaican.app import GenericHandler - - -class OrderHandler(GenericHandler): - """ - Base class and default implementation for the :term:`order - handler`. - - This is responsible for business logic involving customer orders - after they have been first created. (The :term:`new order batch` - handler is responsible for creation logic.) - """ - - def get_order_qty_uom_text(self, order_qty, order_uom, case_size=None, html=False): - """ - Return the display text for a given order quantity. - - Default logic will return something like ``"3 Cases (x 6 = 18 - Units)"``. - - :param order_qty: Numeric quantity. - - :param order_uom: An order UOM constant; should be something - from :data:`~sideshow.enum.ORDER_UOM`. - - :param case_size: Case size for the product, if known. - - :param html: Whether the return value should include any HTML. - If false (the default), it will be plain text only. If - true, will replace the ``x`` character with ``×``. - - :returns: Display text. - """ - enum = self.app.enum - - if order_uom == enum.ORDER_UOM_CASE: - if case_size is None: - case_qty = unit_qty = '??' - else: - case_qty = self.app.render_quantity(case_size) - unit_qty = self.app.render_quantity(order_qty * case_size) - CS = enum.ORDER_UOM[enum.ORDER_UOM_CASE] - EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT] - order_qty = self.app.render_quantity(order_qty) - times = '×' if html else 'x' - return (f"{order_qty} {CS} ({times} {case_qty} = {unit_qty} {EA})") - - # units - unit_qty = self.app.render_quantity(order_qty) - EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT] - return f"{unit_qty} {EA}" - - def item_status_to_variant(self, status_code): - """ - Return a Buefy style variant for the given status code. - - Default logic will return ``None`` for "normal" item status, - but may return ``'warning'`` for some (e.g. canceled). - - :param status_code: The status code for an order item. - - :returns: Style variant string (e.g. ``'warning'``) or - ``None``. - """ - enum = self.app.enum - if status_code in (enum.ORDER_ITEM_STATUS_CANCELED, - enum.ORDER_ITEM_STATUS_REFUND_PENDING, - enum.ORDER_ITEM_STATUS_REFUNDED, - enum.ORDER_ITEM_STATUS_RESTOCKED, - enum.ORDER_ITEM_STATUS_EXPIRED, - enum.ORDER_ITEM_STATUS_INACTIVE): - return 'warning' - - def process_placement(self, items, user, vendor_name=None, po_number=None, note=None): - """ - Process the "placement" step for the given order items. - - This may eventually do something involving an *actual* - purchase order, or at least a minimal representation of one, - but for now it does not. - - Instead, this will simply update each item to indicate its new - status. A note will be attached to indicate the vendor and/or - PO number, if provided. - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param vendor_name: Name of the vendor to which purchase order - is placed, if known. - - :param po_number: Purchase order number, if known. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - placed = None - if vendor_name: - placed = f"PO {po_number or ''} for vendor {vendor_name}" - elif po_number: - placed = f"PO {po_number}" - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_PLACED, user, note=placed) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_PLACED - - def process_receiving(self, items, user, vendor_name=None, - invoice_number=None, po_number=None, note=None): - """ - Process the "receiving" step for the given order items. - - This will update the status for each item, to indicate they - are "received". - - TODO: This also should email the customer notifying their - items are ready for pickup etc. - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param vendor_name: Name of the vendor, if known. - - :param po_number: Purchase order number, if known. - - :param invoice_number: Invoice number, if known. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - received = None - if invoice_number and po_number and vendor_name: - received = f"invoice {invoice_number} (PO {po_number}) from vendor {vendor_name}" - elif invoice_number and vendor_name: - received = f"invoice {invoice_number} from vendor {vendor_name}" - elif po_number and vendor_name: - received = f"PO {po_number} from vendor {vendor_name}" - elif vendor_name: - received = f"from vendor {vendor_name}" - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_RECEIVED, user, note=received) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_RECEIVED - - def process_reorder(self, items, user, note=None): - """ - Process the "reorder" step for the given order items. - - This will update the status for each item, to indicate they - are "ready" (again) for placement. - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_REORDER, user) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_READY - - def process_contact_success(self, items, user, note=None): - """ - Process the "successful contact" step for the given order - items. - - This will update the status for each item, to indicate they - are "contacted" and awaiting delivery. - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_CONTACTED, user) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_CONTACTED - - def process_contact_failure(self, items, user, note=None): - """ - Process the "failed contact" step for the given order items. - - This will update the status for each item, to indicate - "contact failed". - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_CONTACT_FAILED, user) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_CONTACT_FAILED - - def process_delivery(self, items, user, note=None): - """ - Process the "delivery" step for the given order items. - - This will update the status for each item, to indicate they - are "delivered". - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_DELIVERED, user) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_DELIVERED - - def process_restock(self, items, user, note=None): - """ - Process the "restock" step for the given order items. - - This will update the status for each item, to indicate they - are "restocked". - - :param items: Sequence of - :class:`~sideshow.db.model.orders.OrderItem` records. - - :param user: - :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` - performing the action. - - :param note: Optional *additional* note to be attached to each - order item. - """ - enum = self.app.enum - - for item in items: - item.add_event(enum.ORDER_ITEM_EVENT_RESTOCKED, user) - if note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, user, note=note) - item.status_code = enum.ORDER_ITEM_STATUS_RESTOCKED diff --git a/src/sideshow/web/menus.py b/src/sideshow/web/menus.py index 1641c72..c8256fc 100644 --- a/src/sideshow/web/menus.py +++ b/src/sideshow/web/menus.py @@ -57,27 +57,6 @@ class SideshowMenuHandler(base.MenuHandler): 'perm': 'orders.create', }, {'type': 'sep'}, - { - 'title': "Placement", - 'route': 'order_items_placement', - 'perm': 'order_items_placement.list', - }, - { - 'title': "Receiving", - 'route': 'order_items_receiving', - 'perm': 'order_items_receiving.list', - }, - { - 'title': "Contact", - 'route': 'order_items_contact', - 'perm': 'order_items_contact.list', - }, - { - 'title': "Delivery", - 'route': 'order_items_delivery', - 'perm': 'order_items_delivery.list', - }, - {'type': 'sep'}, { 'title': "All Order Items", 'route': 'order_items', diff --git a/src/sideshow/web/templates/contact/index.mako b/src/sideshow/web/templates/contact/index.mako deleted file mode 100644 index bd8e05f..0000000 --- a/src/sideshow/web/templates/contact/index.mako +++ /dev/null @@ -1,159 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/master/index.mako" /> - -<%def name="render_grid_tag()"> - % if master.has_perm('process_contact'): - ${grid.render_vue_tag(**{'@process-contact-success': "processContactSuccessInit", '@process-contact-failure': "processContactFailureInit"})} - % else: - ${grid.render_vue_tag()} - % endif - - -<%def name="page_content()"> - ${parent.page_content()} - - % if master.has_perm('process_contact'): - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processContactSuccessShowDialog" - % else: - :active.sync="processContactSuccessShowDialog" - % endif - > - - - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processContactFailureShowDialog" - % else: - :active.sync="processContactFailureShowDialog" - % endif - > - - - - % endif - - -<%def name="modify_vue_vars()"> - ${parent.modify_vue_vars()} - % if master.has_perm('process_contact'): - - % endif - diff --git a/src/sideshow/web/templates/delivery/index.mako b/src/sideshow/web/templates/delivery/index.mako deleted file mode 100644 index 251c0f9..0000000 --- a/src/sideshow/web/templates/delivery/index.mako +++ /dev/null @@ -1,173 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/master/index.mako" /> - -<%def name="render_grid_tag()"> - % if master.has_perm('process_delivery') and master.has_perm('process_restock'): - ${grid.render_vue_tag(**{'@process-delivery': "processDeliveryInit", '@process-restock': "processRestockInit"})} - % elif master.has_perm('process_delivery'): - ${grid.render_vue_tag(**{'@process-delivery': "processDeliveryInit"})} - % elif master.has_perm('process_restock'): - ${grid.render_vue_tag(**{'@process-restock': "processRestockInit"})} - % else: - ${grid.render_vue_tag()} - % endif - - -<%def name="page_content()"> - ${parent.page_content()} - - % if master.has_perm('process_delivery'): - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processDeliveryShowDialog" - % else: - :active.sync="processDeliveryShowDialog" - % endif - > - - - - % endif - - % if master.has_perm('process_restock'): - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processRestockShowDialog" - % else: - :active.sync="processRestockShowDialog" - % endif - > - - - - % endif - - -<%def name="modify_vue_vars()"> - ${parent.modify_vue_vars()} - - diff --git a/src/sideshow/web/templates/order-items/view.mako b/src/sideshow/web/templates/order-items/view.mako deleted file mode 100644 index 201540a..0000000 --- a/src/sideshow/web/templates/order-items/view.mako +++ /dev/null @@ -1,411 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/master/view.mako" /> - -<%def name="content_title()"> - (${app.enum.ORDER_ITEM_STATUS[item.status_code]}) - ${instance_title} - - -<%def name="extra_styles()"> - ${parent.extra_styles()} - - - -<%def name="page_content()"> -
-
- - - - - -
-
- - - - - -
-
- -
- -
- - - -<%def name="render_vue_templates()"> - ${parent.render_vue_templates()} - ${events_grid.render_vue_template()} - - -<%def name="modify_vue_vars()"> - ${parent.modify_vue_vars()} - - - -<%def name="make_vue_components()"> - ${parent.make_vue_components()} - ${events_grid.render_vue_finalize()} - diff --git a/src/sideshow/web/templates/orders/create.mako b/src/sideshow/web/templates/orders/create.mako index 28f4e6b..7ed78db 100644 --- a/src/sideshow/web/templates/orders/create.mako +++ b/src/sideshow/web/templates/orders/create.mako @@ -81,7 +81,6 @@
-
@@ -144,7 +143,7 @@ icon-pack="fas" icon-left="redo" :disabled="refreshingCustomer"> - {{ refreshingCustomer ? "Working, please wait..." : "Refresh" }} + {{ refreshingCustomer ? "Refreshing" : "Refresh" }}
@@ -349,7 +348,6 @@ >
- <${b}-tabs :animated="false" % if request.use_oruga: @@ -831,7 +829,6 @@ batchTotalPriceDisplay: ${json.dumps(normalized_batch['total_price_display'])|n}, customerPanelOpen: false, - customerLoading: false, customerIsKnown: ${json.dumps(customer_is_known)|n}, customerID: ${json.dumps(customer_id)|n}, customerName: ${json.dumps(customer_name)|n}, @@ -856,7 +853,6 @@ editItemRow: null, editItemShowDialog: false, - editItemLoading: false, itemDialogSaving: false, % if request.use_oruga: itemDialogTab: 'product', @@ -1136,7 +1132,6 @@ }, customerChanged(customerID, callback) { - this.customerLoading = true const params = {} if (customerID) { @@ -1154,9 +1149,7 @@ if (callback) { callback() } - this.customerLoading = false }, response => { - this.customerLoading = false this.$buefy.toast.open({ message: "Update failed: " + (response.data.error || "(unknown error)"), type: 'is-danger', @@ -1166,9 +1159,9 @@ }, refreshCustomer() { - this.refreshingCustomer = true + this.refreshingContact = true this.customerChanged(this.customerID, () => { - this.refreshingCustomer = false + this.refreshingContact = false this.$buefy.toast.open({ message: "Customer info has been refreshed.", type: 'is-success', @@ -1328,7 +1321,6 @@ productChanged(productID) { if (productID) { - this.editItemLoading = true const params = { action: 'get_product_info', product_id: productID, @@ -1372,11 +1364,8 @@ this.refreshProductDescription += 1 this.refreshTotalPrice += 1 - this.editItemLoading = false - }, response => { this.clearProduct() - this.editItemLoading = false }) } else { this.clearProduct() @@ -1525,7 +1514,6 @@ itemDialogAttemptSave() { this.itemDialogSaving = true - this.editItemLoading = true const params = { order_qty: this.productQuantity, @@ -1563,11 +1551,9 @@ this.batchTotalPriceDisplay = response.data.batch.total_price_display this.itemDialogSaving = false - this.editItemLoading = false this.editItemShowDialog = false }, response => { this.itemDialogSaving = false - this.editItemLoading = false }) }, diff --git a/src/sideshow/web/templates/placement/index.mako b/src/sideshow/web/templates/placement/index.mako deleted file mode 100644 index b7fb1f6..0000000 --- a/src/sideshow/web/templates/placement/index.mako +++ /dev/null @@ -1,104 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/master/index.mako" /> - -<%def name="render_grid_tag()"> - % if master.has_perm('process_placement'): - ${grid.render_vue_tag(**{'@process-placement': "processPlacementInit"})} - % else: - ${grid.render_vue_tag()} - % endif - - -<%def name="page_content()"> - ${parent.page_content()} - % if master.has_perm('process_placement'): - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processPlacementShowDialog" - % else: - :active.sync="processPlacementShowDialog" - % endif - > - - - % endif - - -<%def name="modify_vue_vars()"> - ${parent.modify_vue_vars()} - % if master.has_perm('process_placement'): - - % endif - diff --git a/src/sideshow/web/templates/receiving/index.mako b/src/sideshow/web/templates/receiving/index.mako deleted file mode 100644 index 08ff8e7..0000000 --- a/src/sideshow/web/templates/receiving/index.mako +++ /dev/null @@ -1,192 +0,0 @@ -## -*- coding: utf-8; -*- -<%inherit file="/master/index.mako" /> - -<%def name="render_grid_tag()"> - % if master.has_perm('process_receiving') and master.has_perm('process_reorder'): - ${grid.render_vue_tag(**{'@process-receiving': "processReceivingInit", '@process-reorder': "processReorderInit"})} - % elif master.has_perm('process_receiving'): - ${grid.render_vue_tag(**{'@process-receiving': "processReceivingInit"})} - % elif master.has_perm('process_reorder'): - ${grid.render_vue_tag(**{'@process-reorder': "processReorderInit"})} - % else: - ${grid.render_vue_tag()} - % endif - - -<%def name="page_content()"> - ${parent.page_content()} - - % if master.has_perm('process_receiving'): - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processReceivingShowDialog" - % else: - :active.sync="processReceivingShowDialog" - % endif - > - - - - % endif - - % if master.has_perm('process_reorder'): - - <${b}-modal has-modal-card - % if request.use_oruga: - v-model:active="processReorderShowDialog" - % else: - :active.sync="processReorderShowDialog" - % endif - > - - - - % endif - - -<%def name="modify_vue_vars()"> - ${parent.modify_vue_vars()} - - diff --git a/src/sideshow/web/views/orders.py b/src/sideshow/web/views/orders.py index cf0c054..68ad984 100644 --- a/src/sideshow/web/views/orders.py +++ b/src/sideshow/web/views/orders.py @@ -28,16 +28,12 @@ import decimal import logging import colander -import sqlalchemy as sa from sqlalchemy import orm -from webhelpers2.html import tags, HTML - from wuttaweb.views import MasterView from wuttaweb.forms.schema import UserRef, WuttaMoney, WuttaQuantity, WuttaEnum, WuttaDictEnum from sideshow.db.model import Order, OrderItem -from sideshow.orders import OrderHandler from sideshow.batch.neworder import NewOrderBatchHandler from sideshow.web.forms.schema import (OrderRef, LocalCustomerRef, LocalProductRef, @@ -62,16 +58,10 @@ class OrderView(MasterView): Note that the "edit" view is not exposed here; user must perform various other workflow actions to modify the order. - .. attribute:: order_handler - - Reference to the :term:`order handler` as returned by - :meth:`get_order_handler()`. This gets set in the constructor. - .. attribute:: batch_handler - Reference to the :term:`new order batch` handler, as returned - by :meth:`get_batch_handler()`. This gets set in the - constructor. + Reference to the new order batch handler, as returned by + :meth:`get_batch_handler()`. This gets set in the constructor. """ model_class = Order editable = False @@ -152,22 +142,21 @@ class OrderView(MasterView): 'unit_price_reg', ] - def __init__(self, request, context=None): - super().__init__(request, context=context) - self.order_handler = self.get_order_handler() + def configure_grid(self, g): + """ """ + super().configure_grid(g) - def get_order_handler(self): - """ - Returns the configured :term:`order handler`. + # order_id + g.set_link('order_id') - You normally would not need to call this, and can use - :attr:`order_handler` instead. + # customer_id + g.set_link('customer_id') - :rtype: :class:`~sideshow.orders.OrderHandler` - """ - if hasattr(self, 'order_handler'): - return self.order_handler - return OrderHandler(self.config) + # customer_name + g.set_link('customer_name') + + # total_price + g.set_renderer('total_price', g.render_currency) def get_batch_handler(self): """ @@ -185,22 +174,6 @@ class OrderView(MasterView): return self.batch_handler return self.app.get_batch_handler('neworder') - def configure_grid(self, g): - """ """ - super().configure_grid(g) - - # order_id - g.set_link('order_id') - - # customer_id - g.set_link('customer_id') - - # customer_name - g.set_link('customer_name') - - # total_price - g.set_renderer('total_price', g.render_currency) - def create(self): """ Instead of the typical "create" view, this displays a "wizard" @@ -697,6 +670,8 @@ class OrderView(MasterView): def normalize_row(self, row): """ """ + enum = self.app.enum + data = { 'uuid': row.uuid.hex, 'sequence': row.sequence, @@ -775,8 +750,21 @@ class OrderView(MasterView): } # display text for order qty/uom - data['order_qty_display'] = self.order_handler.get_order_qty_uom_text( - row.order_qty, row.order_uom, case_size=row.case_size, html=True) + if row.order_uom == enum.ORDER_UOM_CASE: + order_qty = self.app.render_quantity(row.order_qty) + if row.case_size is None: + case_qty = unit_qty = '??' + else: + case_qty = self.app.render_quantity(row.case_size) + unit_qty = self.app.render_quantity(row.order_qty * row.case_size) + CS = enum.ORDER_UOM[enum.ORDER_UOM_CASE] + EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT] + data['order_qty_display'] = (f"{order_qty} {CS} " + f"(× {case_qty} = {unit_qty} {EA})") + else: + unit_qty = self.app.render_quantity(row.order_qty) + EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT] + data['order_qty_display'] = f"{unit_qty} {EA}" return data @@ -835,7 +823,7 @@ class OrderView(MasterView): def configure_row_grid(self, g): """ """ super().configure_row_grid(g) - # enum = self.app.enum + enum = self.app.enum # sequence g.set_label('sequence', "Seq.", column_only=True) @@ -863,15 +851,6 @@ class OrderView(MasterView): # status_code g.set_renderer('status_code', self.render_status_code) - # TODO: upstream should set this automatically - g.row_class = self.row_grid_row_class - - def row_grid_row_class(self, item, data, i): - """ """ - variant = self.order_handler.item_status_to_variant(item.status_code) - if variant: - return f'has-background-{variant}' - def render_status_code(self, item, key, value): """ """ enum = self.app.enum @@ -978,26 +957,11 @@ class OrderItemView(MasterView): * ``/order-items/`` * ``/order-items/XXX`` - This class serves both as a proper master view (for "all" order - items) as well as a base class for other "workflow" master views, - each of which auto-filters by order item status: - - * :class:`PlacementView` - * :class:`ReceivingView` - * :class:`ContactView` - * :class:`DeliveryView` - Note that this does not expose create, edit or delete. The user must perform various other workflow actions to modify the item. - - .. attribute:: order_handler - - Reference to the :term:`order handler` as returned by - :meth:`get_order_handler()`. """ model_class = OrderItem - model_title = "Order Item (All)" - model_title_plural = "Order Items (All)" + model_title = "Order Item" route_prefix = 'order_items' url_prefix = '/order-items' creatable = False @@ -1066,29 +1030,6 @@ class OrderItemView(MasterView): 'payment_transaction_number', ] - def __init__(self, request, context=None): - super().__init__(request, context=context) - self.order_handler = self.get_order_handler() - - def get_order_handler(self): - """ - Returns the configured :term:`order handler`. - - You normally would not need to call this, and can use - :attr:`order_handler` instead. - - :rtype: :class:`~sideshow.orders.OrderHandler` - """ - if hasattr(self, 'order_handler'): - return self.order_handler - return OrderHandler(self.config) - - def get_fallback_templates(self, template): - """ """ - templates = super().get_fallback_templates(template) - templates.insert(0, f'/order-items/{template}.mako') - return templates - def get_query(self, session=None): """ """ query = super().get_query(session=session) @@ -1143,11 +1084,12 @@ class OrderItemView(MasterView): enum = self.app.enum return enum.ORDER_ITEM_STATUS[value] - def grid_row_class(self, item, data, i): + def get_instance_title(self, item): """ """ - variant = self.order_handler.item_status_to_variant(item.status_code) - if variant: - return f'has-background-{variant}' + enum = self.app.enum + title = str(item) + status = enum.ORDER_ITEM_STATUS[item.status_code] + return f"({status}) {title}" def configure_form(self, f): """ """ @@ -1197,54 +1139,6 @@ class OrderItemView(MasterView): # paid_amount f.set_node('paid_amount', WuttaMoney(self.request)) - def get_template_context(self, context): - """ """ - if self.viewing: - model = self.app.model - enum = self.app.enum - route_prefix = self.get_route_prefix() - item = context['instance'] - form = context['form'] - - context['item'] = item - context['order'] = item.order - context['order_qty_uom_text'] = self.order_handler.get_order_qty_uom_text( - item.order_qty, item.order_uom, case_size=item.case_size, html=True) - context['item_status_variant'] = self.order_handler.item_status_to_variant(item.status_code) - - grid = self.make_grid(key=f'{route_prefix}.view.events', - model_class=model.OrderItemEvent, - data=item.events, - columns=[ - 'occurred', - 'actor', - 'type_code', - 'note', - ], - labels={ - 'occurred': "Date/Time", - 'actor': "User", - 'type_code': "Event Type", - }) - grid.set_renderer('type_code', lambda e, k, v: enum.ORDER_ITEM_EVENT[v]) - grid.set_renderer('note', self.render_event_note) - if self.request.has_perm('users.view'): - grid.set_renderer('actor', lambda e, k, v: tags.link_to( - e.actor, self.request.route_url('users.view', uuid=e.actor.uuid))) - form.add_grid_vue_context(grid) - context['events_grid'] = grid - - return context - - def render_event_note(self, event, key, value): - """ """ - enum = self.app.enum - if event.type_code == enum.ORDER_ITEM_EVENT_NOTE_ADDED: - return HTML.tag('span', class_='has-background-info-light', - style='padding: 0.25rem 0.5rem;', - c=[value]) - return value - def get_xref_buttons(self, item): """ """ buttons = super().get_xref_buttons(item) @@ -1257,685 +1151,6 @@ class OrderItemView(MasterView): return buttons - def add_note(self): - """ - View which adds a note to an order item. This is POST-only; - will redirect back to the item view. - """ - enum = self.app.enum - item = self.get_instance() - - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, self.request.user, - note=self.request.POST['note']) - - return self.redirect(self.get_action_url('view', item)) - - def change_status(self): - """ - View which changes status for an order item. This is - POST-only; will redirect back to the item view. - """ - model = self.app.model - enum = self.app.enum - main_item = self.get_instance() - session = self.Session() - redirect = self.redirect(self.get_action_url('view', main_item)) - - extra_note = self.request.POST.get('note') - - # validate new status - new_status_code = int(self.request.POST['new_status']) - if new_status_code not in enum.ORDER_ITEM_STATUS: - self.request.session.flash("Invalid status code", 'error') - return redirect - new_status_text = enum.ORDER_ITEM_STATUS[new_status_code] - - # locate all items to which new status will be applied - items = [main_item] - # uuids = self.request.POST.get('uuids') - # if uuids: - # for uuid in uuids.split(','): - # item = Session.get(model.OrderItem, uuid) - # if item: - # items.append(item) - - # update item(s) - for item in items: - if item.status_code != new_status_code: - - # event: change status - note = 'status changed from "{}" to "{}"'.format( - enum.ORDER_ITEM_STATUS[item.status_code], - new_status_text) - item.add_event(enum.ORDER_ITEM_EVENT_STATUS_CHANGE, - self.request.user, note=note) - - # event: add note - if extra_note: - item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, - self.request.user, note=extra_note) - - # new status - item.status_code = new_status_code - - self.request.session.flash(f"Status has been updated to: {new_status_text}") - return redirect - - def get_order_items(self, uuids): - """ - This method provides common logic to fetch a list of order - items based on a list of UUID keys. It is used by various - workflow action methods. - - Note that if no order items are found, this will set a flash - warning message and raise a redirect back to the index page. - - :param uuids: List (or comma-delimited string) of UUID keys. - - :returns: List of :class:`~sideshow.db.model.orders.OrderItem` - records. - """ - model = self.app.model - session = self.Session() - - if uuids is None: - uuids = [] - elif isinstance(uuids, str): - uuids = uuids.split(',') - - items = [] - for uuid in uuids: - if isinstance(uuid, str): - uuid = uuid.strip() - if uuid: - try: - item = session.get(model.OrderItem, uuid) - except sa.exc.StatementError: - pass # nb. invalid UUID - else: - if item: - items.append(item) - - if not items: - self.request.session.flash("Must specify valid order item(s).", 'warning') - raise self.redirect(self.get_index_url()) - - return items - - @classmethod - def defaults(cls, config): - """ """ - cls._order_item_defaults(config) - cls._defaults(config) - - @classmethod - def _order_item_defaults(cls, config): - """ """ - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - instance_url_prefix = cls.get_instance_url_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) - - # add note - config.add_route(f'{route_prefix}.add_note', - f'{instance_url_prefix}/add_note', - request_method='POST') - config.add_view(cls, attr='add_note', - route_name=f'{route_prefix}.add_note', - renderer='json', - permission=f'{permission_prefix}.add_note') - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.add_note', - f"Add note for {model_title}") - - # change status - config.add_route(f'{route_prefix}.change_status', - f'{instance_url_prefix}/change-status', - request_method='POST') - config.add_view(cls, attr='change_status', - route_name=f'{route_prefix}.change_status', - renderer='json', - permission=f'{permission_prefix}.change_status') - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.change_status', - f"Change status for {model_title}") - - -class PlacementView(OrderItemView): - """ - Master view for the "placement" phase of - :class:`~sideshow.db.model.orders.OrderItem`; route prefix is - ``placement``. This is a subclass of :class:`OrderItemView`. - - This class auto-filters so only order items with the following - status codes are shown: - - * :data:`~sideshow.enum.ORDER_ITEM_STATUS_READY` - - Notable URLs provided by this class: - - * ``/placement/`` - * ``/placement/XXX`` - """ - model_title = "Order Item (Placement)" - model_title_plural = "Order Items (Placement)" - route_prefix = 'order_items_placement' - url_prefix = '/placement' - - def get_query(self, session=None): - """ """ - query = super().get_query(session=session) - model = self.app.model - enum = self.app.enum - return query.filter(model.OrderItem.status_code == enum.ORDER_ITEM_STATUS_READY) - - def configure_grid(self, g): - """ """ - super().configure_grid(g) - - # checkable - if self.has_perm('process_placement'): - g.checkable = True - - # tool button: Order Placed - if self.has_perm('process_placement'): - button = self.make_button("Order Placed", primary=True, - icon_left='arrow-circle-right', - **{'@click': "$emit('process-placement', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_placement') - - def process_placement(self): - """ - View to process the "placement" step for some order item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param vendor_name: Optional name of vendor. - - :param po_number: Optional PO number. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_placement()` on - the :attr:`~OrderItemView.order_handler`, then redirects user - back to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - vendor_name = self.request.POST.get('vendor_name', '').strip() or None - po_number = self.request.POST.get('po_number', '').strip() or None - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_placement(items, self.request.user, - vendor_name=vendor_name, - po_number=po_number, - note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as placed") - return self.redirect(self.get_index_url()) - - @classmethod - def defaults(cls, config): - cls._order_item_defaults(config) - cls._placement_defaults(config) - cls._defaults(config) - - @classmethod - def _placement_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - url_prefix = cls.get_url_prefix() - model_title_plural = cls.get_model_title_plural() - - # process placement - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.process_placement', - f"Process placement for {model_title_plural}") - config.add_route(f'{route_prefix}.process_placement', - f'{url_prefix}/process-placement', - request_method='POST') - config.add_view(cls, attr='process_placement', - route_name=f'{route_prefix}.process_placement', - permission=f'{permission_prefix}.process_placement') - - -class ReceivingView(OrderItemView): - """ - Master view for the "receiving" phase of - :class:`~sideshow.db.model.orders.OrderItem`; route prefix is - ``receiving``. This is a subclass of :class:`OrderItemView`. - - This class auto-filters so only order items with the following - status codes are shown: - - * :data:`~sideshow.enum.ORDER_ITEM_STATUS_PLACED` - - Notable URLs provided by this class: - - * ``/receiving/`` - * ``/receiving/XXX`` - """ - model_title = "Order Item (Receiving)" - model_title_plural = "Order Items (Receiving)" - route_prefix = 'order_items_receiving' - url_prefix = '/receiving' - - def get_query(self, session=None): - """ """ - query = super().get_query(session=session) - model = self.app.model - enum = self.app.enum - return query.filter(model.OrderItem.status_code == enum.ORDER_ITEM_STATUS_PLACED) - - def configure_grid(self, g): - """ """ - super().configure_grid(g) - - # checkable - if self.has_any_perm('process_receiving', 'process_reorder'): - g.checkable = True - - # tool button: Received - if self.has_perm('process_receiving'): - button = self.make_button("Received", primary=True, - icon_left='arrow-circle-right', - **{'@click': "$emit('process-receiving', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_receiving') - - # tool button: Re-Order - if self.has_perm('process_reorder'): - button = self.make_button("Re-Order", - icon_left='redo', - **{'@click': "$emit('process-reorder', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_reorder') - - def process_receiving(self): - """ - View to process the "receiving" step for some order item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param vendor_name: Optional name of vendor. - - :param invoice_number: Optional invoice number. - - :param po_number: Optional PO number. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_receiving()` on - the :attr:`~OrderItemView.order_handler`, then redirects user - back to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - vendor_name = self.request.POST.get('vendor_name', '').strip() or None - invoice_number = self.request.POST.get('invoice_number', '').strip() or None - po_number = self.request.POST.get('po_number', '').strip() or None - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_receiving(items, self.request.user, - vendor_name=vendor_name, - invoice_number=invoice_number, - po_number=po_number, - note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as received") - return self.redirect(self.get_index_url()) - - def process_reorder(self): - """ - View to process the "reorder" step for some order item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_reorder()` on the - :attr:`~OrderItemView.order_handler`, then redirects user back - to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_reorder(items, self.request.user, note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as ready for placement") - return self.redirect(self.get_index_url()) - - @classmethod - def defaults(cls, config): - cls._order_item_defaults(config) - cls._receiving_defaults(config) - cls._defaults(config) - - @classmethod - def _receiving_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - url_prefix = cls.get_url_prefix() - model_title_plural = cls.get_model_title_plural() - - # process receiving - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.process_receiving', - f"Process receiving for {model_title_plural}") - config.add_route(f'{route_prefix}.process_receiving', - f'{url_prefix}/process-receiving', - request_method='POST') - config.add_view(cls, attr='process_receiving', - route_name=f'{route_prefix}.process_receiving', - permission=f'{permission_prefix}.process_receiving') - - # process reorder - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.process_reorder', - f"Process re-order for {model_title_plural}") - config.add_route(f'{route_prefix}.process_reorder', - f'{url_prefix}/process-reorder', - request_method='POST') - config.add_view(cls, attr='process_reorder', - route_name=f'{route_prefix}.process_reorder', - permission=f'{permission_prefix}.process_reorder') - - -class ContactView(OrderItemView): - """ - Master view for the "contact" phase of - :class:`~sideshow.db.model.orders.OrderItem`; route prefix is - ``contact``. This is a subclass of :class:`OrderItemView`. - - This class auto-filters so only order items with the following - status codes are shown: - - * :data:`~sideshow.enum.ORDER_ITEM_STATUS_RECEIVED` - * :data:`~sideshow.enum.ORDER_ITEM_STATUS_CONTACT_FAILED` - - Notable URLs provided by this class: - - * ``/contact/`` - * ``/contact/XXX`` - """ - model_title = "Order Item (Contact)" - model_title_plural = "Order Items (Contact)" - route_prefix = 'order_items_contact' - url_prefix = '/contact' - - def get_query(self, session=None): - """ """ - query = super().get_query(session=session) - model = self.app.model - enum = self.app.enum - return query.filter(model.OrderItem.status_code.in_(( - enum.ORDER_ITEM_STATUS_RECEIVED, - enum.ORDER_ITEM_STATUS_CONTACT_FAILED))) - - def configure_grid(self, g): - """ """ - super().configure_grid(g) - - # checkable - if self.has_perm('process_contact'): - g.checkable = True - - # tool button: Contact Success - if self.has_perm('process_contact'): - button = self.make_button("Contact Success", primary=True, - icon_left='phone', - **{'@click': "$emit('process-contact-success', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_contact_success') - - # tool button: Contact Failure - if self.has_perm('process_contact'): - button = self.make_button("Contact Failure", variant='is-warning', - icon_left='phone', - **{'@click': "$emit('process-contact-failure', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_contact_failure') - - def process_contact_success(self): - """ - View to process the "contact success" step for some order - item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_contact_success()` - on the :attr:`~OrderItemView.order_handler`, then redirects - user back to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_contact_success(items, self.request.user, note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as contacted") - return self.redirect(self.get_index_url()) - - def process_contact_failure(self): - """ - View to process the "contact failure" step for some order - item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_contact_failure()` - on the :attr:`~OrderItemView.order_handler`, then redirects - user back to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_contact_failure(items, self.request.user, note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as contact failed") - return self.redirect(self.get_index_url()) - - @classmethod - def defaults(cls, config): - cls._order_item_defaults(config) - cls._contact_defaults(config) - cls._defaults(config) - - @classmethod - def _contact_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - url_prefix = cls.get_url_prefix() - model_title_plural = cls.get_model_title_plural() - - # common perm for processing contact success + failure - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.process_contact', - f"Process contact success/failure for {model_title_plural}") - - # process contact success - config.add_route(f'{route_prefix}.process_contact_success', - f'{url_prefix}/process-contact-success', - request_method='POST') - config.add_view(cls, attr='process_contact_success', - route_name=f'{route_prefix}.process_contact_success', - permission=f'{permission_prefix}.process_contact') - - # process contact failure - config.add_route(f'{route_prefix}.process_contact_failure', - f'{url_prefix}/process-contact-failure', - request_method='POST') - config.add_view(cls, attr='process_contact_failure', - route_name=f'{route_prefix}.process_contact_failure', - permission=f'{permission_prefix}.process_contact') - - -class DeliveryView(OrderItemView): - """ - Master view for the "delivery" phase of - :class:`~sideshow.db.model.orders.OrderItem`; route prefix is - ``delivery``. This is a subclass of :class:`OrderItemView`. - - This class auto-filters so only order items with the following - status codes are shown: - - * :data:`~sideshow.enum.ORDER_ITEM_STATUS_RECEIVED` - * :data:`~sideshow.enum.ORDER_ITEM_STATUS_CONTACTED` - - Notable URLs provided by this class: - - * ``/delivery/`` - * ``/delivery/XXX`` - """ - model_title = "Order Item (Delivery)" - model_title_plural = "Order Items (Delivery)" - route_prefix = 'order_items_delivery' - url_prefix = '/delivery' - - def get_query(self, session=None): - """ """ - query = super().get_query(session=session) - model = self.app.model - enum = self.app.enum - return query.filter(model.OrderItem.status_code.in_(( - enum.ORDER_ITEM_STATUS_RECEIVED, - enum.ORDER_ITEM_STATUS_CONTACTED))) - - def configure_grid(self, g): - """ """ - super().configure_grid(g) - - # checkable - if self.has_any_perm('process_delivery', 'process_restock'): - g.checkable = True - - # tool button: Delivered - if self.has_perm('process_delivery'): - button = self.make_button("Delivered", primary=True, - icon_left='check', - **{'@click': "$emit('process-delivery', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_delivery') - - # tool button: Restocked - if self.has_perm('process_restock'): - button = self.make_button("Restocked", - icon_left='redo', - **{'@click': "$emit('process-restock', checkedRows)", - ':disabled': '!checkedRows.length'}) - g.add_tool(button, key='process_restock') - - def process_delivery(self): - """ - View to process the "delivery" step for some order item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_delivery()` on - the :attr:`~OrderItemView.order_handler`, then redirects user - back to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_delivery(items, self.request.user, note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as delivered") - return self.redirect(self.get_index_url()) - - def process_restock(self): - """ - View to process the "restock" step for some order item(s). - - This requires a POST request with data: - - :param item_uuids: Comma-delimited list of - :class:`~sideshow.db.model.orders.OrderItem` UUID keys. - - :param note: Optional note text from the user. - - This invokes - :meth:`~sideshow.orders.OrderHandler.process_restock()` on the - :attr:`~OrderItemView.order_handler`, then redirects user back - to the index page. - """ - items = self.get_order_items(self.request.POST.get('item_uuids', '')) - note = self.request.POST.get('note', '').strip() or None - - self.order_handler.process_restock(items, self.request.user, note=note) - - self.request.session.flash(f"{len(items)} Order Items were marked as restocked") - return self.redirect(self.get_index_url()) - - @classmethod - def defaults(cls, config): - cls._order_item_defaults(config) - cls._delivery_defaults(config) - cls._defaults(config) - - @classmethod - def _delivery_defaults(cls, config): - route_prefix = cls.get_route_prefix() - permission_prefix = cls.get_permission_prefix() - url_prefix = cls.get_url_prefix() - model_title_plural = cls.get_model_title_plural() - - # process delivery - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.process_delivery', - f"Process delivery for {model_title_plural}") - config.add_route(f'{route_prefix}.process_delivery', - f'{url_prefix}/process-delivery', - request_method='POST') - config.add_view(cls, attr='process_delivery', - route_name=f'{route_prefix}.process_delivery', - permission=f'{permission_prefix}.process_delivery') - - # process restock - config.add_wutta_permission(permission_prefix, - f'{permission_prefix}.process_restock', - f"Process restock for {model_title_plural}") - config.add_route(f'{route_prefix}.process_restock', - f'{url_prefix}/process-restock', - request_method='POST') - config.add_view(cls, attr='process_restock', - route_name=f'{route_prefix}.process_restock', - permission=f'{permission_prefix}.process_restock') - def defaults(config, **kwargs): base = globals() @@ -1946,18 +1161,6 @@ def defaults(config, **kwargs): OrderItemView = kwargs.get('OrderItemView', base['OrderItemView']) OrderItemView.defaults(config) - PlacementView = kwargs.get('PlacementView', base['PlacementView']) - PlacementView.defaults(config) - - ReceivingView = kwargs.get('ReceivingView', base['ReceivingView']) - ReceivingView.defaults(config) - - ContactView = kwargs.get('ContactView', base['ContactView']) - ContactView.defaults(config) - - DeliveryView = kwargs.get('DeliveryView', base['DeliveryView']) - DeliveryView.defaults(config) - def includeme(config): defaults(config) diff --git a/tests/batch/test_neworder.py b/tests/batch/test_neworder.py index 42e42dc..b3fbf4a 100644 --- a/tests/batch/test_neworder.py +++ b/tests/batch/test_neworder.py @@ -1109,20 +1109,6 @@ class TestNewOrderBatchHandler(DataTestCase): self.assertEqual(item.unit_cost, decimal.Decimal('3.99')) self.assertEqual(item.unit_price_reg, decimal.Decimal('5.99')) - def test_set_initial_item_status(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - user = model.User(username='barney') - item = model.OrderItem() - self.assertIsNone(item.status_code) - self.assertEqual(len(item.events), 0) - handler.set_initial_item_status(item, user) - self.assertEqual(item.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(len(item.events), 2) - self.assertEqual(item.events[0].type_code, enum.ORDER_ITEM_EVENT_INITIATED) - self.assertEqual(item.events[1].type_code, enum.ORDER_ITEM_EVENT_READY) - def test_execute(self): model = self.app.model enum = self.app.enum diff --git a/tests/db/model/test_orders.py b/tests/db/model/test_orders.py index 21ee153..7169991 100644 --- a/tests/db/model/test_orders.py +++ b/tests/db/model/test_orders.py @@ -19,11 +19,6 @@ class TestOrder(DataTestCase): class TestOrderItem(DataTestCase): - def make_config(self, **kw): - config = super().make_config(**kw) - config.setdefault('wutta.enum_spec', 'sideshow.enum') - return config - def test_full_description(self): item = mod.OrderItem() @@ -49,15 +44,3 @@ class TestOrderItem(DataTestCase): product_description='Vinegar', product_size='32oz') self.assertEqual(str(item), "Bragg Vinegar 32oz") - - def test_add_event(self): - model = self.app.model - enum = self.app.enum - user = model.User(username='barney') - item = mod.OrderItem() - self.assertEqual(item.events, []) - item.add_event(enum.ORDER_ITEM_EVENT_INITIATED, user) - item.add_event(enum.ORDER_ITEM_EVENT_READY, user) - self.assertEqual(len(item.events), 2) - self.assertEqual(item.events[0].type_code, enum.ORDER_ITEM_EVENT_INITIATED) - self.assertEqual(item.events[1].type_code, enum.ORDER_ITEM_EVENT_READY) diff --git a/tests/test_orders.py b/tests/test_orders.py deleted file mode 100644 index 5937045..0000000 --- a/tests/test_orders.py +++ /dev/null @@ -1,419 +0,0 @@ -# -*- coding: utf-8; -*- - -from wuttjamaican.testing import DataTestCase - -from sideshow import orders as mod - - -class TestOrderHandler(DataTestCase): - - def make_config(self, **kwargs): - config = super().make_config(**kwargs) - config.setdefault('wutta.model_spec', 'sideshow.db.model') - config.setdefault('wutta.enum_spec', 'sideshow.enum') - return config - - def make_handler(self): - return mod.OrderHandler(self.config) - - def test_get_order_qty_uom_text(self): - enum = self.app.enum - handler = self.make_handler() - - # typical, plain text - text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_CASE, case_size=12) - self.assertEqual(text, "2 Cases (x 12 = 24 Units)") - - # typical w/ html - text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_CASE, case_size=12, html=True) - self.assertEqual(text, "2 Cases (× 12 = 24 Units)") - - # unknown case size - text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_CASE) - self.assertEqual(text, "2 Cases (x ?? = ?? Units)") - text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_CASE, html=True) - self.assertEqual(text, "2 Cases (× ?? = ?? Units)") - - # units only - text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_UNIT) - self.assertEqual(text, "2 Units") - text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_UNIT, html=True) - self.assertEqual(text, "2 Units") - - def test_item_status_to_variant(self): - enum = self.app.enum - handler = self.make_handler() - - # typical - self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_INITIATED)) - self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_READY)) - self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_PLACED)) - self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_RECEIVED)) - self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_CONTACTED)) - self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_PAID)) - - # warning - self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_CANCELED), 'warning') - self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_REFUND_PENDING), 'warning') - self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_REFUNDED), 'warning') - self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_RESTOCKED), 'warning') - self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_EXPIRED), 'warning') - self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_INACTIVE), 'warning') - - def test_process_placement(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_READY) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_READY) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_READY) - order.items.append(item3) - self.session.add(order) - self.session.flush() - - # two items are updated - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(len(item1.events), 0) - self.assertEqual(len(item2.events), 0) - handler.process_placement([item1, item2], user, - vendor_name="Acme Dist", po_number='ACME123') - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item1.events), 1) - self.assertEqual(len(item2.events), 1) - self.assertEqual(item1.events[0].note, "PO ACME123 for vendor Acme Dist") - self.assertEqual(item2.events[0].note, "PO ACME123 for vendor Acme Dist") - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_PLACED) - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_PLACED) - - # update last item, without vendor name but extra note - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(len(item3.events), 0) - handler.process_placement([item3], user, po_number="939234", note="extra note") - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item3.events), 2) - self.assertEqual(item3.events[0].note, "PO 939234") - self.assertEqual(item3.events[1].note, "extra note") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_PLACED) - self.assertEqual(item3.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) - - def test_process_receiving(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item3) - item4 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item4) - item5 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item5) - self.session.add(order) - self.session.flush() - - # all info provided - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item1.events), 0) - handler.process_receiving([item1], user, vendor_name="Acme Dist", - invoice_number='INV123', po_number='123') - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item1.events), 1) - self.assertEqual(item1.events[0].note, "invoice INV123 (PO 123) from vendor Acme Dist") - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_RECEIVED) - - # missing PO number - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item2.events), 0) - handler.process_receiving([item2], user, vendor_name="Acme Dist", invoice_number='INV123') - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item2.events), 1) - self.assertEqual(item2.events[0].note, "invoice INV123 from vendor Acme Dist") - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_RECEIVED) - - # missing invoice number - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item3.events), 0) - handler.process_receiving([item3], user, vendor_name="Acme Dist", po_number='123') - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item3.events), 1) - self.assertEqual(item3.events[0].note, "PO 123 from vendor Acme Dist") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_RECEIVED) - - # vendor name only - self.assertEqual(item4.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item4.events), 0) - handler.process_receiving([item4], user, vendor_name="Acme Dist") - self.assertEqual(item4.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item4.events), 1) - self.assertEqual(item4.events[0].note, "from vendor Acme Dist") - self.assertEqual(item4.events[0].type_code, enum.ORDER_ITEM_EVENT_RECEIVED) - - # no info; extra note - self.assertEqual(item5.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item5.events), 0) - handler.process_receiving([item5], user, note="extra note") - self.assertEqual(item5.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item5.events), 2) - self.assertIsNone(item5.events[0].note) - self.assertEqual(item5.events[0].type_code, enum.ORDER_ITEM_EVENT_RECEIVED) - self.assertEqual(item5.events[1].note, "extra note") - self.assertEqual(item5.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) - - def test_process_reorder(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_PLACED) - order.items.append(item3) - self.session.add(order) - self.session.flush() - - # two items are updated - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item1.events), 0) - self.assertEqual(len(item2.events), 0) - handler.process_reorder([item1, item2], user) - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(len(item1.events), 1) - self.assertEqual(len(item2.events), 1) - self.assertIsNone(item1.events[0].note) - self.assertIsNone(item2.events[0].note) - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_REORDER) - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_REORDER) - - # update last item, with extra note - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_PLACED) - self.assertEqual(len(item3.events), 0) - handler.process_reorder([item3], user, note="extra note") - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_READY) - self.assertEqual(len(item3.events), 2) - self.assertIsNone(item3.events[0].note) - self.assertEqual(item3.events[1].note, "extra note") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_REORDER) - self.assertEqual(item3.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) - - def test_process_contact_success(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item3) - self.session.add(order) - self.session.flush() - - # two items are updated - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item1.events), 0) - self.assertEqual(len(item2.events), 0) - handler.process_contact_success([item1, item2], user) - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_CONTACTED) - self.assertEqual(len(item1.events), 1) - self.assertEqual(len(item2.events), 1) - self.assertIsNone(item1.events[0].note) - self.assertIsNone(item2.events[0].note) - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACTED) - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACTED) - - # update last item, with extra note - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item3.events), 0) - handler.process_contact_success([item3], user, note="extra note") - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_CONTACTED) - self.assertEqual(len(item3.events), 2) - self.assertIsNone(item3.events[0].note) - self.assertEqual(item3.events[1].note, "extra note") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACTED) - self.assertEqual(item3.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) - - def test_process_contact_failure(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item3) - self.session.add(order) - self.session.flush() - - # two items are updated - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item1.events), 0) - self.assertEqual(len(item2.events), 0) - handler.process_contact_failure([item1, item2], user) - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACT_FAILED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_CONTACT_FAILED) - self.assertEqual(len(item1.events), 1) - self.assertEqual(len(item2.events), 1) - self.assertIsNone(item1.events[0].note) - self.assertIsNone(item2.events[0].note) - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACT_FAILED) - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACT_FAILED) - - # update last item, with extra note - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item3.events), 0) - handler.process_contact_failure([item3], user, note="extra note") - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_CONTACT_FAILED) - self.assertEqual(len(item3.events), 2) - self.assertIsNone(item3.events[0].note) - self.assertEqual(item3.events[1].note, "extra note") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACT_FAILED) - self.assertEqual(item3.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) - - def test_process_delivery(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item3) - self.session.add(order) - self.session.flush() - - # two items are updated - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item1.events), 0) - self.assertEqual(len(item2.events), 0) - handler.process_delivery([item1, item2], user) - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_DELIVERED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_DELIVERED) - self.assertEqual(len(item1.events), 1) - self.assertEqual(len(item2.events), 1) - self.assertIsNone(item1.events[0].note) - self.assertIsNone(item2.events[0].note) - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_DELIVERED) - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_DELIVERED) - - # update last item, with extra note - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item3.events), 0) - handler.process_delivery([item3], user, note="extra note") - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_DELIVERED) - self.assertEqual(len(item3.events), 2) - self.assertIsNone(item3.events[0].note) - self.assertEqual(item3.events[1].note, "extra note") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_DELIVERED) - self.assertEqual(item3.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) - - def test_process_restock(self): - model = self.app.model - enum = self.app.enum - handler = self.make_handler() - - # sample data - user = model.User(username='barney') - self.session.add(user) - order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) - item1 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item1) - item2 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item2) - item3 = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, - status_code=enum.ORDER_ITEM_STATUS_RECEIVED) - order.items.append(item3) - self.session.add(order) - self.session.flush() - - # two items are updated - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item1.events), 0) - self.assertEqual(len(item2.events), 0) - handler.process_restock([item1, item2], user) - self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RESTOCKED) - self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_RESTOCKED) - self.assertEqual(len(item1.events), 1) - self.assertEqual(len(item2.events), 1) - self.assertIsNone(item1.events[0].note) - self.assertIsNone(item2.events[0].note) - self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_RESTOCKED) - self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_RESTOCKED) - - # update last item, with extra note - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_RECEIVED) - self.assertEqual(len(item3.events), 0) - handler.process_restock([item3], user, note="extra note") - self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_RESTOCKED) - self.assertEqual(len(item3.events), 2) - self.assertIsNone(item3.events[0].note) - self.assertEqual(item3.events[1].note, "extra note") - self.assertEqual(item3.events[0].type_code, enum.ORDER_ITEM_EVENT_RESTOCKED) - self.assertEqual(item3.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED) diff --git a/tests/web/views/test_orders.py b/tests/web/views/test_orders.py index 3d2a8e4..89d5039 100644 --- a/tests/web/views/test_orders.py +++ b/tests/web/views/test_orders.py @@ -11,7 +11,6 @@ from pyramid.response import Response from wuttaweb.forms.schema import WuttaMoney from sideshow.batch.neworder import NewOrderBatchHandler -from sideshow.orders import OrderHandler from sideshow.testing import WebTestCase from sideshow.web.views import orders as mod from sideshow.web.forms.schema import OrderRef, PendingProductRef @@ -31,13 +30,6 @@ class TestOrderView(WebTestCase): def make_handler(self): return NewOrderBatchHandler(self.config) - def test_order_handler(self): - view = self.make_view() - handler = view.order_handler - self.assertIsInstance(handler, OrderHandler) - handler2 = view.get_order_handler() - self.assertIs(handler2, handler) - def test_configure_grid(self): model = self.app.model view = self.make_view() @@ -1183,19 +1175,6 @@ class TestOrderView(WebTestCase): view.configure_row_grid(grid) self.assertIn('product_scancode', grid.linked_columns) - def test_row_grid_row_class(self): - model = self.app.model - enum = self.app.enum - view = self.make_view() - - # typical - item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_READY) - self.assertIsNone(view.row_grid_row_class(item, {}, 1)) - - # warning - item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_CANCELED) - self.assertEqual(view.row_grid_row_class(item, {}, 1), 'has-background-warning') - def test_render_status_code(self): enum = self.app.enum view = self.make_view() @@ -1265,28 +1244,17 @@ class TestOrderView(WebTestCase): self.assertTrue(self.session.query(model.Setting).count() > 1) -class OrderItemViewTestMixin: +class TestOrderItemView(WebTestCase): - def test_common_order_handler(self): - view = self.make_view() - handler = view.order_handler - self.assertIsInstance(handler, OrderHandler) - handler2 = view.get_order_handler() - self.assertIs(handler2, handler) + def make_view(self): + return mod.OrderItemView(self.request) - def test_common_get_fallback_templates(self): - view = self.make_view() - - templates = view.get_fallback_templates('view') - self.assertEqual(templates, ['/order-items/view.mako', - '/master/view.mako']) - - def test_common_get_query(self): + def test_get_query(self): view = self.make_view() query = view.get_query(session=self.session) self.assertIsInstance(query, orm.Query) - def test_common_configure_grid(self): + def test_configure_grid(self): model = self.app.model view = self.make_view() grid = view.make_grid(model_class=model.OrderItem) @@ -1294,7 +1262,7 @@ class OrderItemViewTestMixin: view.configure_grid(grid) self.assertIn('order_id', grid.linked_columns) - def test_common_render_order_id(self): + def test_render_order_id(self): model = self.app.model view = self.make_view() order = model.Order(order_id=42) @@ -1302,26 +1270,25 @@ class OrderItemViewTestMixin: order.items.append(item) self.assertEqual(view.render_order_id(item, None, None), 42) - def test_common_render_status_code(self): + def test_render_status_code(self): enum = self.app.enum view = self.make_view() self.assertEqual(view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED), 'initiated') - def test_common_grid_row_class(self): + def test_get_instance_title(self): model = self.app.model enum = self.app.enum view = self.make_view() - # typical - item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_READY) - self.assertIsNone(view.grid_row_class(item, {}, 1)) + item = model.OrderItem(product_brand='Bragg', + product_description='Vinegar', + product_size='32oz', + status_code=enum.ORDER_ITEM_STATUS_INITIATED) + title = view.get_instance_title(item) + self.assertEqual(title, "(initiated) Bragg Vinegar 32oz") - # warning - item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_CANCELED) - self.assertEqual(view.grid_row_class(item, {}, 1), 'has-background-warning') - - def test_common_configure_form(self): + def test_configure_form(self): model = self.app.model enum = self.app.enum view = self.make_view() @@ -1347,45 +1314,7 @@ class OrderItemViewTestMixin: self.assertIsInstance(schema['order'].typ, OrderRef) self.assertNotIn('pending_product', form) - def test_common_get_template_context(self): - model = self.app.model - enum = self.app.enum - view = self.make_view() - - order = model.Order() - item = model.OrderItem(order_qty=2, order_uom=enum.ORDER_UOM_CASE, case_size=8) - order.items.append(item) - - with patch.object(self.request, 'is_root', new=True): - with patch.object(view, 'viewing', new=True): - form = view.make_model_form(model_instance=item) - context = view.get_template_context({'instance': item, 'form': form}) - self.assertIn('item', context) - self.assertIs(context['item'], item) - self.assertIn('order', context) - self.assertIs(context['order'], order) - self.assertIn('order_qty_uom_text', context) - self.assertEqual(context['order_qty_uom_text'], "2 Cases (× 8 = 16 Units)") - - def test_common_render_event_note(self): - model = self.app.model - enum = self.app.enum - view = self.make_view() - - # typical - event = model.OrderItemEvent(type_code=enum.ORDER_ITEM_EVENT_READY, note='testing') - result = view.render_event_note(event, 'note', 'testing') - self.assertEqual(result, 'testing') - - # user note - event = model.OrderItemEvent(type_code=enum.ORDER_ITEM_EVENT_NOTE_ADDED, note='testing2') - result = view.render_event_note(event, 'note', 'testing2') - self.assertNotEqual(result, 'testing2') - self.assertIn('