fix: customize "view order item" page w/ panels
more to come soon..
This commit is contained in:
parent
13d576295e
commit
c79b0262f3
6
docs/api/sideshow.orders.rst
Normal file
6
docs/api/sideshow.orders.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``sideshow.orders``
|
||||
===================
|
||||
|
||||
.. automodule:: sideshow.orders
|
||||
:members:
|
|
@ -45,12 +45,27 @@ 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 item>`.
|
||||
|
||||
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
|
||||
|
|
|
@ -39,6 +39,7 @@ 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
|
||||
|
|
|
@ -44,6 +44,9 @@ 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
|
||||
|
||||
|
|
77
src/sideshow/orders.py
Normal file
77
src/sideshow/orders.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Sideshow -- Case/Special Order Tracker
|
||||
# Copyright © 2024-2025 Lance Edgar
|
||||
#
|
||||
# This file is part of Sideshow.
|
||||
#
|
||||
# Sideshow is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Sideshow is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Sideshow. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
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}"
|
154
src/sideshow/web/templates/order-items/view.mako
Normal file
154
src/sideshow/web/templates/order-items/view.mako
Normal file
|
@ -0,0 +1,154 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<%inherit file="/master/view.mako" />
|
||||
|
||||
<%def name="extra_styles()">
|
||||
${parent.extra_styles()}
|
||||
<style>
|
||||
|
||||
.field .field-label .label {
|
||||
white-space: nowrap;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</%def>
|
||||
|
||||
<%def name="page_content()">
|
||||
<div style="padding: 2rem; display: flex; justify-content: space-evenly; gap: 2rem;">
|
||||
<div style="flex-grow: 1;">
|
||||
|
||||
<nav class="panel" style="width: 100%;">
|
||||
<p class="panel-heading">Order Item</p>
|
||||
<div class="panel-block">
|
||||
<div style="width: 100%;">
|
||||
<b-field horizontal label="ID">
|
||||
<span>Order ID ${order.order_id} — Item #${item.sequence}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Order Qty">
|
||||
<span>${order_qty_uom_text|n}</span>
|
||||
</b-field>
|
||||
% if item.discount_percent:
|
||||
<b-field horizontal label="Discount">
|
||||
<span>${app.render_percent(item.discount_percent)}</span>
|
||||
</b-field>
|
||||
% endif
|
||||
<b-field horizontal label="Total Due">
|
||||
<span>${app.render_currency(item.total_price)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Total Paid">
|
||||
<span>${app.render_currency(item.paid_amount)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Status">
|
||||
<span>${app.enum.ORDER_ITEM_STATUS[item.status_code]}</span>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="panel" style="width: 100%;">
|
||||
<p class="panel-heading">Pricing</p>
|
||||
<div class="panel-block">
|
||||
<div style="width: 100%;">
|
||||
<b-field horizontal label="Unit Cost">
|
||||
<span>${app.render_currency(item.unit_cost, scale=4)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Unit Price Reg.">
|
||||
<span>${app.render_currency(item.unit_price_reg)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Unit Price Sale">
|
||||
<span>${app.render_currency(item.unit_price_sale)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Sale Ends">
|
||||
<span>${app.render_datetime(item.sale_ends)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Unit Price Quoted">
|
||||
<span>${app.render_currency(item.unit_price_quoted)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Case Size">
|
||||
<span>${app.render_quantity(item.case_size)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Case Price Quoted">
|
||||
<span>${app.render_currency(item.case_price_quoted)}</span>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
<div style="flex-grow: 1;">
|
||||
|
||||
<nav class="panel" style="width: 100%;">
|
||||
<p class="panel-heading">Customer</p>
|
||||
<div class="panel-block">
|
||||
<div style="width: 100%;">
|
||||
<b-field horizontal label="Customer ID">
|
||||
<span>${order.customer_id}</span>
|
||||
</b-field>
|
||||
% if not order.customer_id and order.local_customer:
|
||||
<b-field horizontal label="Local Customer">
|
||||
<span>${h.link_to(order.local_customer, url('local_customers.view', uuid=order.local_customer.uuid))}</span>
|
||||
</b-field>
|
||||
% endif
|
||||
% if not order.customer_id and order.pending_customer:
|
||||
<b-field horizontal label="Pending Customer">
|
||||
<span>${h.link_to(order.pending_customer, url('pending_customers.view', uuid=order.pending_customer.uuid))}</span>
|
||||
</b-field>
|
||||
% endif
|
||||
<b-field horizontal label="Customer Name">
|
||||
<span>${order.customer_name}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Phone Number">
|
||||
<span>${order.phone_number}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Email Address">
|
||||
<span>${order.email_address}</span>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="panel" style="width: 100%;">
|
||||
<p class="panel-heading">Product</p>
|
||||
<div class="panel-block">
|
||||
<div style="width: 100%;">
|
||||
<b-field horizontal label="Product ID">
|
||||
<span>${item.product_id}</span>
|
||||
</b-field>
|
||||
% if not item.product_id and item.local_product:
|
||||
<b-field horizontal label="Local Product">
|
||||
<span>${h.link_to(item.local_product, url('local_products.view', uuid=order.local_product.uuid))}</span>
|
||||
</b-field>
|
||||
% endif
|
||||
% if not item.product_id and item.pending_product:
|
||||
<b-field horizontal label="Pending Product">
|
||||
<span>${h.link_to(item.pending_product, url('pending_products.view', uuid=order.pending_product.uuid))}</span>
|
||||
</b-field>
|
||||
% endif
|
||||
<b-field horizontal label="Scancode">
|
||||
<span>${item.product_scancode}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Brand">
|
||||
<span>${item.product_brand}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Description">
|
||||
<span>${item.product_description}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Size">
|
||||
<span>${item.product_size}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Sold by Weight">
|
||||
<span>${app.render_boolean(item.product_weighed)}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Department">
|
||||
<span>${item.department_name}</span>
|
||||
</b-field>
|
||||
<b-field horizontal label="Special Order">
|
||||
<span>${app.render_boolean(item.special_order)}</span>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</%def>
|
|
@ -34,6 +34,7 @@ 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,
|
||||
|
@ -58,10 +59,16 @@ 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 new order batch handler, as returned by
|
||||
:meth:`get_batch_handler()`. This gets set in the constructor.
|
||||
Reference to the :term:`new order batch` handler, as returned
|
||||
by :meth:`get_batch_handler()`. This gets set in the
|
||||
constructor.
|
||||
"""
|
||||
model_class = Order
|
||||
editable = False
|
||||
|
@ -142,21 +149,22 @@ class OrderView(MasterView):
|
|||
'unit_price_reg',
|
||||
]
|
||||
|
||||
def configure_grid(self, g):
|
||||
""" """
|
||||
super().configure_grid(g)
|
||||
def __init__(self, request, context=None):
|
||||
super().__init__(request, context=context)
|
||||
self.order_handler = self.get_order_handler()
|
||||
|
||||
# order_id
|
||||
g.set_link('order_id')
|
||||
def get_order_handler(self):
|
||||
"""
|
||||
Returns the configured :term:`order handler`.
|
||||
|
||||
# customer_id
|
||||
g.set_link('customer_id')
|
||||
You normally would not need to call this, and can use
|
||||
:attr:`order_handler` instead.
|
||||
|
||||
# customer_name
|
||||
g.set_link('customer_name')
|
||||
|
||||
# total_price
|
||||
g.set_renderer('total_price', g.render_currency)
|
||||
:rtype: :class:`~sideshow.orders.OrderHandler`
|
||||
"""
|
||||
if hasattr(self, 'order_handler'):
|
||||
return self.order_handler
|
||||
return OrderHandler(self.config)
|
||||
|
||||
def get_batch_handler(self):
|
||||
"""
|
||||
|
@ -174,6 +182,22 @@ 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"
|
||||
|
@ -670,8 +694,6 @@ class OrderView(MasterView):
|
|||
|
||||
def normalize_row(self, row):
|
||||
""" """
|
||||
enum = self.app.enum
|
||||
|
||||
data = {
|
||||
'uuid': row.uuid.hex,
|
||||
'sequence': row.sequence,
|
||||
|
@ -750,21 +772,8 @@ class OrderView(MasterView):
|
|||
}
|
||||
|
||||
# display text for order qty/uom
|
||||
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}"
|
||||
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)
|
||||
|
||||
return data
|
||||
|
||||
|
@ -823,7 +832,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)
|
||||
|
@ -959,6 +968,11 @@ class OrderItemView(MasterView):
|
|||
|
||||
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"
|
||||
|
@ -1030,6 +1044,23 @@ 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_query(self, session=None):
|
||||
""" """
|
||||
query = super().get_query(session=session)
|
||||
|
@ -1139,6 +1170,17 @@ class OrderItemView(MasterView):
|
|||
# paid_amount
|
||||
f.set_node('paid_amount', WuttaMoney(self.request))
|
||||
|
||||
def get_template_context(self, context):
|
||||
""" """
|
||||
if self.viewing:
|
||||
item = context['instance']
|
||||
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)
|
||||
|
||||
return context
|
||||
|
||||
def get_xref_buttons(self, item):
|
||||
""" """
|
||||
buttons = super().get_xref_buttons(item)
|
||||
|
|
40
tests/test_orders.py
Normal file
40
tests/test_orders.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# -*- 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.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")
|
|
@ -11,6 +11,7 @@ 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
|
||||
|
@ -30,6 +31,13 @@ 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()
|
||||
|
@ -1249,6 +1257,13 @@ class TestOrderItemView(WebTestCase):
|
|||
def make_view(self):
|
||||
return mod.OrderItemView(self.request)
|
||||
|
||||
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_get_query(self):
|
||||
view = self.make_view()
|
||||
query = view.get_query(session=self.session)
|
||||
|
@ -1314,6 +1329,24 @@ class TestOrderItemView(WebTestCase):
|
|||
self.assertIsInstance(schema['order'].typ, OrderRef)
|
||||
self.assertNotIn('pending_product', form)
|
||||
|
||||
def test_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(view, 'viewing', new=True):
|
||||
context = view.get_template_context({'instance': item})
|
||||
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_get_xref_buttons(self):
|
||||
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
|
||||
model = self.app.model
|
||||
|
|
Loading…
Reference in a new issue