fix: customize "view order item" page w/ panels
more to come soon..
This commit is contained in:
		
							parent
							
								
									13d576295e
								
							
						
					
					
						commit
						c79b0262f3
					
				
					 9 changed files with 403 additions and 32 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue