feat: add initial workflow master views, UI features
This commit is contained in:
		
							parent
							
								
									9d378a0c5f
								
							
						
					
					
						commit
						7167c6a7cc
					
				
					 10 changed files with 2364 additions and 22 deletions
				
			
		| 
						 | 
				
			
			@ -285,6 +285,12 @@ 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
 | 
			
		||||
| 
						 | 
				
			
			@ -366,6 +372,7 @@ ORDER_ITEM_EVENT = OrderedDict([
 | 
			
		|||
    (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"),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -96,3 +96,215 @@ class OrderHandler(GenericHandler):
 | 
			
		|||
                           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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,6 +57,27 @@ 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',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										159
									
								
								src/sideshow/web/templates/contact/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/sideshow/web/templates/contact/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,159 @@
 | 
			
		|||
## -*- 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>
 | 
			
		||||
 | 
			
		||||
<%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
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_contact_success'), ref='processContactSuccessForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processContactSuccessUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Contact Success</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processContactSuccessUuids.length }}
 | 
			
		||||
              item{{ processContactSuccessUuids.length > 1 ? 's' : '' }}
 | 
			
		||||
              as being "contacted".
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processContactSuccessNote"
 | 
			
		||||
                       ref="processContactSuccessNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processContactSuccessSubmit()"
 | 
			
		||||
                      :disabled="processContactSuccessSubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processContactSuccessSubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processContactSuccessShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
 | 
			
		||||
      <${b}-modal has-modal-card
 | 
			
		||||
                  % if request.use_oruga:
 | 
			
		||||
                      v-model:active="processContactFailureShowDialog"
 | 
			
		||||
                  % else:
 | 
			
		||||
                      :active.sync="processContactFailureShowDialog"
 | 
			
		||||
                  % endif
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_contact_failure'), ref='processContactFailureForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processContactFailureUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Contact Failure</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processContactFailureUuids.length }}
 | 
			
		||||
              item{{ processContactFailureUuids.length > 1 ? 's' : '' }}
 | 
			
		||||
              as being "contact failed".
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processContactFailureNote"
 | 
			
		||||
                       ref="processContactFailureNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processContactFailureSubmit()"
 | 
			
		||||
                      :disabled="processContactFailureSubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processContactFailureSubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processContactFailureShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="modify_vue_vars()">
 | 
			
		||||
  ${parent.modify_vue_vars()}
 | 
			
		||||
  % if master.has_perm('process_contact'):
 | 
			
		||||
      <script>
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processContactSuccessShowDialog = false
 | 
			
		||||
        ThisPageData.processContactSuccessUuids = []
 | 
			
		||||
        ThisPageData.processContactSuccessNote = null
 | 
			
		||||
        ThisPageData.processContactSuccessSubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processContactSuccessInit = function(items) {
 | 
			
		||||
            this.processContactSuccessUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processContactSuccessNote = null
 | 
			
		||||
            this.processContactSuccessShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processContactSuccessNote.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processContactSuccessSubmit = function() {
 | 
			
		||||
            this.processContactSuccessSubmitting = true
 | 
			
		||||
            this.$refs.processContactSuccessForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processContactFailureShowDialog = false
 | 
			
		||||
        ThisPageData.processContactFailureUuids = []
 | 
			
		||||
        ThisPageData.processContactFailureNote = null
 | 
			
		||||
        ThisPageData.processContactFailureSubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processContactFailureInit = function(items) {
 | 
			
		||||
            this.processContactFailureUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processContactFailureNote = null
 | 
			
		||||
            this.processContactFailureShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processContactFailureNote.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processContactFailureSubmit = function() {
 | 
			
		||||
            this.processContactFailureSubmitting = true
 | 
			
		||||
            this.$refs.processContactFailureForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      </script>
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
							
								
								
									
										173
									
								
								src/sideshow/web/templates/delivery/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/sideshow/web/templates/delivery/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,173 @@
 | 
			
		|||
## -*- 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>
 | 
			
		||||
 | 
			
		||||
<%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
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_delivery'), ref='processDeliveryForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processDeliveryUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Delivery</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processDeliveryUuids.length }}
 | 
			
		||||
              item{{ processDeliveryUuids.length > 1 ? 's' : '' }}
 | 
			
		||||
              as being "delivered".
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processDeliveryNote"
 | 
			
		||||
                       ref="processDeliveryNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processDeliverySubmit()"
 | 
			
		||||
                      :disabled="processDeliverySubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processDeliverySubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processDeliveryShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
 | 
			
		||||
  % 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
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_restock'), ref='processRestockForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processRestockUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Restock</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processRestockUuids.length }}
 | 
			
		||||
              item{{ processRestockUuids.length > 1 ? 's' : '' }}
 | 
			
		||||
              as being "restocked".
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processRestockNote"
 | 
			
		||||
                       ref="processRestockNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processRestockSubmit()"
 | 
			
		||||
                      :disabled="processRestockSubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processRestockSubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processRestockShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="modify_vue_vars()">
 | 
			
		||||
  ${parent.modify_vue_vars()}
 | 
			
		||||
  <script>
 | 
			
		||||
 | 
			
		||||
    % if master.has_perm('process_delivery'):
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processDeliveryShowDialog = false
 | 
			
		||||
        ThisPageData.processDeliveryUuids = []
 | 
			
		||||
        ThisPageData.processDeliveryNote = null
 | 
			
		||||
        ThisPageData.processDeliverySubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processDeliveryInit = function(items) {
 | 
			
		||||
            this.processDeliveryUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processDeliveryNote = null
 | 
			
		||||
            this.processDeliveryShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processDeliveryNote.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processDeliverySubmit = function() {
 | 
			
		||||
            this.processDeliverySubmitting = true
 | 
			
		||||
            this.$refs.processDeliveryForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    % endif
 | 
			
		||||
 | 
			
		||||
    % if master.has_perm('process_restock'):
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processRestockShowDialog = false
 | 
			
		||||
        ThisPageData.processRestockUuids = []
 | 
			
		||||
        ThisPageData.processRestockNote = null
 | 
			
		||||
        ThisPageData.processRestockSubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processRestockInit = function(items) {
 | 
			
		||||
            this.processRestockUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processRestockNote = null
 | 
			
		||||
            this.processRestockShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processRestockNote.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processRestockSubmit = function() {
 | 
			
		||||
            this.processRestockSubmitting = true
 | 
			
		||||
            this.$refs.processRestockForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    % endif
 | 
			
		||||
 | 
			
		||||
  </script>
 | 
			
		||||
</%def>
 | 
			
		||||
							
								
								
									
										104
									
								
								src/sideshow/web/templates/placement/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/sideshow/web/templates/placement/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
## -*- 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>
 | 
			
		||||
 | 
			
		||||
<%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
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_placement'), ref='processPlacementForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processPlacementUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Placement</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processPlacementUuids.length }}
 | 
			
		||||
              item{{ processPlacementUuids.length > 1 ? 's' : '' }} as
 | 
			
		||||
              being "placed" on order from vendor.
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Vendor"
 | 
			
		||||
                     :type="processPlacementVendor ? null : 'is-danger'">
 | 
			
		||||
              <b-input name="vendor_name"
 | 
			
		||||
                       v-model="processPlacementVendor"
 | 
			
		||||
                       ref="processPlacementVendor" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
            <b-field horizontal label="PO Number">
 | 
			
		||||
              <b-input name="po_number"
 | 
			
		||||
                       v-model="processPlacementNumber" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processPlacementNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processPlacementSubmit()"
 | 
			
		||||
                      :disabled="!processPlacementVendor || processPlacementSubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processPlacementSubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processPlacementShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="modify_vue_vars()">
 | 
			
		||||
  ${parent.modify_vue_vars()}
 | 
			
		||||
  % if master.has_perm('process_placement'):
 | 
			
		||||
      <script>
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processPlacementShowDialog = false
 | 
			
		||||
        ThisPageData.processPlacementUuids = []
 | 
			
		||||
        ThisPageData.processPlacementVendor = null
 | 
			
		||||
        ThisPageData.processPlacementNumber = null
 | 
			
		||||
        ThisPageData.processPlacementNote = null
 | 
			
		||||
        ThisPageData.processPlacementSubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processPlacementInit = function(items) {
 | 
			
		||||
            this.processPlacementUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processPlacementVendor = null
 | 
			
		||||
            this.processPlacementNumber = null
 | 
			
		||||
            this.processPlacementNote = null
 | 
			
		||||
            this.processPlacementShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processPlacementVendor.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processPlacementSubmit = function() {
 | 
			
		||||
            this.processPlacementSubmitting = true
 | 
			
		||||
            this.$refs.processPlacementForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      </script>
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
							
								
								
									
										192
									
								
								src/sideshow/web/templates/receiving/index.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								src/sideshow/web/templates/receiving/index.mako
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,192 @@
 | 
			
		|||
## -*- 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>
 | 
			
		||||
 | 
			
		||||
<%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
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_receiving'), ref='processReceivingForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processReceivingUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Receiving</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processReceivingUuids.length }}
 | 
			
		||||
              item{{ processReceivingUuids.length > 1 ? 's' : '' }}
 | 
			
		||||
              as being "received" from vendor.
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Vendor"
 | 
			
		||||
                     :type="processReceivingVendor ? null : 'is-danger'">
 | 
			
		||||
              <b-input name="vendor_name"
 | 
			
		||||
                       v-model="processReceivingVendor"
 | 
			
		||||
                       ref="processReceivingVendor" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
            <b-field horizontal label="Invoice Number">
 | 
			
		||||
              <b-input name="invoice_number"
 | 
			
		||||
                       v-model="processReceivingInvoiceNumber" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
            <b-field horizontal label="PO Number">
 | 
			
		||||
              <b-input name="po_number"
 | 
			
		||||
                       v-model="processReceivingPoNumber" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processReceivingNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processReceivingSubmit()"
 | 
			
		||||
                      :disabled="!processReceivingVendor || processReceivingSubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processReceivingSubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processReceivingShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
 | 
			
		||||
  % 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
 | 
			
		||||
                  >
 | 
			
		||||
        <div class="modal-card">
 | 
			
		||||
          ${h.form(url(f'{route_prefix}.process_reorder'), ref='processReorderForm')}
 | 
			
		||||
          ${h.csrf_token(request)}
 | 
			
		||||
          ${h.hidden('item_uuids', **{':value': 'processReorderUuids.join()'})}
 | 
			
		||||
 | 
			
		||||
          <header class="modal-card-head">
 | 
			
		||||
            <p class="modal-card-title">Process Re-Order</p>
 | 
			
		||||
          </header>
 | 
			
		||||
 | 
			
		||||
          <section class="modal-card-body">
 | 
			
		||||
            <p class="block">
 | 
			
		||||
              This will mark {{ processReorderUuids.length }}
 | 
			
		||||
              item{{ processReorderUuids.length > 1 ? 's' : '' }}
 | 
			
		||||
              as being "ready" for placement (again).
 | 
			
		||||
            </p>
 | 
			
		||||
            <b-field horizontal label="Note">
 | 
			
		||||
              <b-input name="note"
 | 
			
		||||
                       v-model="processReorderNote"
 | 
			
		||||
                       ref="processReorderNote"
 | 
			
		||||
                       type="textarea" />
 | 
			
		||||
            </b-field>
 | 
			
		||||
          </section>
 | 
			
		||||
 | 
			
		||||
          <footer class="modal-card-foot">
 | 
			
		||||
            <b-button type="is-primary"
 | 
			
		||||
                      @click="processReorderSubmit()"
 | 
			
		||||
                      :disabled="processReorderSubmitting"
 | 
			
		||||
                      icon-pack="fas"
 | 
			
		||||
                      icon-left="save">
 | 
			
		||||
              {{ processReorderSubmitting ? "Working, please wait..." : "Save" }}
 | 
			
		||||
            </b-button>
 | 
			
		||||
            <b-button @click="processReorderShowDialog = false">
 | 
			
		||||
              Cancel
 | 
			
		||||
            </b-button>
 | 
			
		||||
          </footer>
 | 
			
		||||
 | 
			
		||||
          ${h.end_form()}
 | 
			
		||||
        </div>
 | 
			
		||||
      </${b}-modal>
 | 
			
		||||
 | 
			
		||||
  % endif
 | 
			
		||||
</%def>
 | 
			
		||||
 | 
			
		||||
<%def name="modify_vue_vars()">
 | 
			
		||||
  ${parent.modify_vue_vars()}
 | 
			
		||||
  <script>
 | 
			
		||||
 | 
			
		||||
    % if master.has_perm('process_receiving'):
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processReceivingShowDialog = false
 | 
			
		||||
        ThisPageData.processReceivingUuids = []
 | 
			
		||||
        ThisPageData.processReceivingVendor = null
 | 
			
		||||
        ThisPageData.processReceivingInvoiceNumber = null
 | 
			
		||||
        ThisPageData.processReceivingPoNumber = null
 | 
			
		||||
        ThisPageData.processReceivingNote = null
 | 
			
		||||
        ThisPageData.processReceivingSubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processReceivingInit = function(items) {
 | 
			
		||||
            this.processReceivingUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processReceivingVendor = null
 | 
			
		||||
            this.processReceivingInvoiceNumber = null
 | 
			
		||||
            this.processReceivingPoNumber = null
 | 
			
		||||
            this.processReceivingNote = null
 | 
			
		||||
            this.processReceivingShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processReceivingVendor.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processReceivingSubmit = function() {
 | 
			
		||||
            this.processReceivingSubmitting = true
 | 
			
		||||
            this.$refs.processReceivingForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    % endif
 | 
			
		||||
 | 
			
		||||
    % if master.has_perm('process_reorder'):
 | 
			
		||||
 | 
			
		||||
        ThisPageData.processReorderShowDialog = false
 | 
			
		||||
        ThisPageData.processReorderUuids = []
 | 
			
		||||
        ThisPageData.processReorderNote = null
 | 
			
		||||
        ThisPageData.processReorderSubmitting = false
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processReorderInit = function(items) {
 | 
			
		||||
            this.processReorderUuids = items.map((item) => item.uuid)
 | 
			
		||||
            this.processReorderNote = null
 | 
			
		||||
            this.processReorderShowDialog = true
 | 
			
		||||
            this.$nextTick(() => {
 | 
			
		||||
                this.$refs.processReorderNote.focus()
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ThisPage.methods.processReorderSubmit = function() {
 | 
			
		||||
            this.processReorderSubmitting = true
 | 
			
		||||
            this.$refs.processReorderForm.submit()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    % endif
 | 
			
		||||
 | 
			
		||||
  </script>
 | 
			
		||||
</%def>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import decimal
 | 
			
		|||
import logging
 | 
			
		||||
 | 
			
		||||
import colander
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
from sqlalchemy import orm
 | 
			
		||||
 | 
			
		||||
from webhelpers2.html import tags, HTML
 | 
			
		||||
| 
						 | 
				
			
			@ -977,6 +978,15 @@ 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.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -986,7 +996,8 @@ class OrderItemView(MasterView):
 | 
			
		|||
       :meth:`get_order_handler()`.
 | 
			
		||||
    """
 | 
			
		||||
    model_class = OrderItem
 | 
			
		||||
    model_title = "Order Item"
 | 
			
		||||
    model_title = "Order Item (All)"
 | 
			
		||||
    model_title_plural = "Order Items (All)"
 | 
			
		||||
    route_prefix = 'order_items'
 | 
			
		||||
    url_prefix = '/order-items'
 | 
			
		||||
    creatable = False
 | 
			
		||||
| 
						 | 
				
			
			@ -1072,6 +1083,12 @@ class OrderItemView(MasterView):
 | 
			
		|||
            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)
 | 
			
		||||
| 
						 | 
				
			
			@ -1304,13 +1321,56 @@ class OrderItemView(MasterView):
 | 
			
		|||
        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()
 | 
			
		||||
| 
						 | 
				
			
			@ -1347,6 +1407,536 @@ class OrderItemView(MasterView):
 | 
			
		|||
                                    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()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1356,6 +1946,18 @@ 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -58,3 +59,361 @@ class TestOrderHandler(DataTestCase):
 | 
			
		|||
        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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1265,24 +1265,28 @@ class TestOrderView(WebTestCase):
 | 
			
		|||
                self.assertTrue(self.session.query(model.Setting).count() > 1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestOrderItemView(WebTestCase):
 | 
			
		||||
class OrderItemViewTestMixin:
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return mod.OrderItemView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_order_handler(self):
 | 
			
		||||
    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 test_get_query(self):
 | 
			
		||||
    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):
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        query = view.get_query(session=self.session)
 | 
			
		||||
        self.assertIsInstance(query, orm.Query)
 | 
			
		||||
 | 
			
		||||
    def test_configure_grid(self):
 | 
			
		||||
    def test_common_configure_grid(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
| 
						 | 
				
			
			@ -1290,7 +1294,7 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
        view.configure_grid(grid)
 | 
			
		||||
        self.assertIn('order_id', grid.linked_columns)
 | 
			
		||||
 | 
			
		||||
    def test_render_order_id(self):
 | 
			
		||||
    def test_common_render_order_id(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        order = model.Order(order_id=42)
 | 
			
		||||
| 
						 | 
				
			
			@ -1298,13 +1302,13 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
        order.items.append(item)
 | 
			
		||||
        self.assertEqual(view.render_order_id(item, None, None), 42)
 | 
			
		||||
 | 
			
		||||
    def test_render_status_code(self):
 | 
			
		||||
    def test_common_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_grid_row_class(self):
 | 
			
		||||
    def test_common_grid_row_class(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
| 
						 | 
				
			
			@ -1317,7 +1321,7 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
        item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_CANCELED)
 | 
			
		||||
        self.assertEqual(view.grid_row_class(item, {}, 1), 'has-background-warning')
 | 
			
		||||
 | 
			
		||||
    def test_configure_form(self):
 | 
			
		||||
    def test_common_configure_form(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
| 
						 | 
				
			
			@ -1343,7 +1347,7 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
            self.assertIsInstance(schema['order'].typ, OrderRef)
 | 
			
		||||
            self.assertNotIn('pending_product', form)
 | 
			
		||||
 | 
			
		||||
    def test_get_template_context(self):
 | 
			
		||||
    def test_common_get_template_context(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
| 
						 | 
				
			
			@ -1363,7 +1367,7 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
                self.assertIn('order_qty_uom_text', context)
 | 
			
		||||
                self.assertEqual(context['order_qty_uom_text'], "2 Cases (× 8 = 16 Units)")
 | 
			
		||||
 | 
			
		||||
    def test_render_event_note(self):
 | 
			
		||||
    def test_common_render_event_note(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
| 
						 | 
				
			
			@ -1381,7 +1385,7 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
        self.assertIn('class="has-background-info-light"', result)
 | 
			
		||||
        self.assertIn('testing2', result)
 | 
			
		||||
 | 
			
		||||
    def test_get_xref_buttons(self):
 | 
			
		||||
    def test_common_get_xref_buttons(self):
 | 
			
		||||
        self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
| 
						 | 
				
			
			@ -1404,11 +1408,12 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
            self.assertEqual(len(buttons), 1)
 | 
			
		||||
            self.assertIn("View the Order", buttons[0])
 | 
			
		||||
 | 
			
		||||
    def test_add_note(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items.view', '/order-items/{uuid}')
 | 
			
		||||
    def test_common_add_note(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        self.pyramid_config.add_route(f'{view.get_route_prefix()}.view',
 | 
			
		||||
                                      f'{view.get_url_prefix()}/{{uuid}}')
 | 
			
		||||
 | 
			
		||||
        user = model.User(username='barney')
 | 
			
		||||
        self.session.add(user)
 | 
			
		||||
| 
						 | 
				
			
			@ -1429,11 +1434,12 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
                    self.assertEqual(item.events[0].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
                    self.assertEqual(item.events[0].note, 'testing')
 | 
			
		||||
 | 
			
		||||
    def test_change_status(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items.view', '/order-items/{uuid}')
 | 
			
		||||
    def test_common_change_status(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        self.pyramid_config.add_route(f'{view.get_route_prefix()}.view',
 | 
			
		||||
                                      f'{view.get_url_prefix()}/{{uuid}}')
 | 
			
		||||
 | 
			
		||||
        user = model.User(username='barney')
 | 
			
		||||
        self.session.add(user)
 | 
			
		||||
| 
						 | 
				
			
			@ -1459,7 +1465,7 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
                        self.assertEqual(len(item.events), 1)
 | 
			
		||||
                        self.assertEqual(item.events[0].type_code, enum.ORDER_ITEM_EVENT_STATUS_CHANGE)
 | 
			
		||||
                        self.assertEqual(item.events[0].note,
 | 
			
		||||
                                         "status changed from 'initiated' to 'placed'")
 | 
			
		||||
                                         'status changed from "initiated" to "placed"')
 | 
			
		||||
 | 
			
		||||
                    # status change plus note
 | 
			
		||||
                    with patch.object(self.request, 'POST', new={
 | 
			
		||||
| 
						 | 
				
			
			@ -1472,10 +1478,10 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
                        self.assertEqual(len(item.events), 3)
 | 
			
		||||
                        self.assertEqual(item.events[0].type_code, enum.ORDER_ITEM_EVENT_STATUS_CHANGE)
 | 
			
		||||
                        self.assertEqual(item.events[0].note,
 | 
			
		||||
                                         "status changed from 'initiated' to 'placed'")
 | 
			
		||||
                                         'status changed from "initiated" to "placed"')
 | 
			
		||||
                        self.assertEqual(item.events[1].type_code, enum.ORDER_ITEM_EVENT_STATUS_CHANGE)
 | 
			
		||||
                        self.assertEqual(item.events[1].note,
 | 
			
		||||
                                         "status changed from 'placed' to 'received'")
 | 
			
		||||
                                         'status changed from "placed" to "received"')
 | 
			
		||||
                        self.assertEqual(item.events[2].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
                        self.assertEqual(item.events[2].note, "check it out")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1486,3 +1492,510 @@ class TestOrderItemView(WebTestCase):
 | 
			
		|||
                        self.assertIsInstance(result, HTTPFound)
 | 
			
		||||
                        self.assertTrue(self.request.session.peek_flash('error'))
 | 
			
		||||
                        self.assertEqual(len(item.events), 3)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestOrderItemView(OrderItemViewTestMixin, WebTestCase):
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return mod.OrderItemView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_get_order_items(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        self.pyramid_config.add_route('order_items', '/order-items/')
 | 
			
		||||
 | 
			
		||||
        user = model.User(username='barney')
 | 
			
		||||
        self.session.add(user)
 | 
			
		||||
        order = model.Order(order_id=42, 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)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
            # no items found
 | 
			
		||||
            self.assertRaises(HTTPFound, view.get_order_items, None)
 | 
			
		||||
            self.assertRaises(HTTPFound, view.get_order_items, '')
 | 
			
		||||
            self.assertRaises(HTTPFound, view.get_order_items, [])
 | 
			
		||||
            self.assertRaises(HTTPFound, view.get_order_items, 'invalid')
 | 
			
		||||
 | 
			
		||||
            # list of UUID
 | 
			
		||||
            items = view.get_order_items([item1.uuid, item2.uuid])
 | 
			
		||||
            self.assertEqual(len(items), 2)
 | 
			
		||||
            self.assertIs(items[0], item1)
 | 
			
		||||
            self.assertIs(items[1], item2)
 | 
			
		||||
 | 
			
		||||
            # list of str
 | 
			
		||||
            items = view.get_order_items([item1.uuid.hex, item2.uuid.hex])
 | 
			
		||||
            self.assertEqual(len(items), 2)
 | 
			
		||||
            self.assertIs(items[0], item1)
 | 
			
		||||
            self.assertIs(items[1], item2)
 | 
			
		||||
 | 
			
		||||
            # comma-delimited str
 | 
			
		||||
            items = view.get_order_items(','.join([item1.uuid.hex, item2.uuid.hex]))
 | 
			
		||||
            self.assertEqual(len(items), 2)
 | 
			
		||||
            self.assertIs(items[0], item1)
 | 
			
		||||
            self.assertIs(items[1], item2)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPlacementView(OrderItemViewTestMixin, WebTestCase):
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return mod.PlacementView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_configure_grid(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # nothing added without perms
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
        view.configure_grid(grid)
 | 
			
		||||
        self.assertFalse(grid.checkable)
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
 | 
			
		||||
        # button added with perm
 | 
			
		||||
        with patch.object(self.request, 'is_root', new=True):
 | 
			
		||||
            view.configure_grid(grid)
 | 
			
		||||
            self.assertTrue(grid.checkable)
 | 
			
		||||
            self.assertEqual(len(grid.tools), 1)
 | 
			
		||||
            self.assertIn('process_placement', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_placement']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Order Placed', tool)
 | 
			
		||||
 | 
			
		||||
    def test_process_placement(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_placement', '/placement/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_placement') as process_placement:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_placement)
 | 
			
		||||
                    process_placement.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # 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(item3.status_code, enum.ORDER_ITEM_STATUS_READY)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                self.assertEqual(len(item2.events), 0)
 | 
			
		||||
                self.assertEqual(len(item3.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': ','.join([item1.uuid.hex, item2.uuid.hex]),
 | 
			
		||||
                        'vendor_name': 'Acme Dist',
 | 
			
		||||
                        'po_number': 'ACME123',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_placement()
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED)
 | 
			
		||||
                self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_PLACED)
 | 
			
		||||
                self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_READY)
 | 
			
		||||
                self.assertEqual(len(item1.events), 1)
 | 
			
		||||
                self.assertEqual(len(item2.events), 1)
 | 
			
		||||
                self.assertEqual(len(item3.events), 0)
 | 
			
		||||
                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.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestReceivingView(OrderItemViewTestMixin, WebTestCase):
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return mod.ReceivingView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_configure_grid(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # nothing added without perms
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
        view.configure_grid(grid)
 | 
			
		||||
        self.assertFalse(grid.checkable)
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
 | 
			
		||||
        # buttons added with perm
 | 
			
		||||
        with patch.object(self.request, 'is_root', new=True):
 | 
			
		||||
            view.configure_grid(grid)
 | 
			
		||||
            self.assertEqual(len(grid.tools), 2)
 | 
			
		||||
            self.assertTrue(grid.checkable)
 | 
			
		||||
 | 
			
		||||
            self.assertIn('process_receiving', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_receiving']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Received', tool)
 | 
			
		||||
 | 
			
		||||
            self.assertIn('process_reorder', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_reorder']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Re-Order', tool)
 | 
			
		||||
 | 
			
		||||
    def test_process_receiving(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_receiving', '/receiving/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_receiving') as process_receiving:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_receiving)
 | 
			
		||||
                    process_receiving.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # all info provided
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': item1.uuid.hex,
 | 
			
		||||
                        'vendor_name': "Acme Dist",
 | 
			
		||||
                        'invoice_number': 'INV123',
 | 
			
		||||
                        'po_number': '123',
 | 
			
		||||
                        'note': 'extra note',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_receiving()
 | 
			
		||||
                self.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 2)
 | 
			
		||||
                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)
 | 
			
		||||
                self.assertEqual(item1.events[1].note, "extra note")
 | 
			
		||||
                self.assertEqual(item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
 | 
			
		||||
    def test_process_reorder(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_receiving', '/receiving/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_reorder') as process_reorder:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_reorder)
 | 
			
		||||
                    process_reorder.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # all info provided
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': item1.uuid.hex,
 | 
			
		||||
                        'note': 'extra note',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_reorder()
 | 
			
		||||
                self.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_READY)
 | 
			
		||||
                self.assertEqual(len(item1.events), 2)
 | 
			
		||||
                self.assertIsNone(item1.events[0].note)
 | 
			
		||||
                self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_REORDER)
 | 
			
		||||
                self.assertEqual(item1.events[1].note, "extra note")
 | 
			
		||||
                self.assertEqual(item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestContactView(OrderItemViewTestMixin, WebTestCase):
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return mod.ContactView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_configure_grid(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # nothing added without perms
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
        view.configure_grid(grid)
 | 
			
		||||
        self.assertFalse(grid.checkable)
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
 | 
			
		||||
        # buttons added with perm
 | 
			
		||||
        with patch.object(self.request, 'is_root', new=True):
 | 
			
		||||
            view.configure_grid(grid)
 | 
			
		||||
            self.assertEqual(len(grid.tools), 2)
 | 
			
		||||
            self.assertTrue(grid.checkable)
 | 
			
		||||
 | 
			
		||||
            self.assertIn('process_contact_success', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_contact_success']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Contact Success', tool)
 | 
			
		||||
 | 
			
		||||
            self.assertIn('process_contact_failure', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_contact_failure']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Contact Failure', tool)
 | 
			
		||||
 | 
			
		||||
    def test_process_contact_success(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_contact', '/contact/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_contact_success') as process_contact_success:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_contact_success)
 | 
			
		||||
                    process_contact_success.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # all info provided
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': item1.uuid.hex,
 | 
			
		||||
                        'note': 'extra note',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_contact_success()
 | 
			
		||||
                self.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 2)
 | 
			
		||||
                self.assertIsNone(item1.events[0].note)
 | 
			
		||||
                self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACTED)
 | 
			
		||||
                self.assertEqual(item1.events[1].note, "extra note")
 | 
			
		||||
                self.assertEqual(item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
 | 
			
		||||
    def test_process_contact_failure(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_contact', '/contact/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_contact_failure') as process_contact_failure:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_contact_failure)
 | 
			
		||||
                    process_contact_failure.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # all info provided
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': item1.uuid.hex,
 | 
			
		||||
                        'note': 'extra note',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_contact_failure()
 | 
			
		||||
                self.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACT_FAILED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 2)
 | 
			
		||||
                self.assertIsNone(item1.events[0].note)
 | 
			
		||||
                self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACT_FAILED)
 | 
			
		||||
                self.assertEqual(item1.events[1].note, "extra note")
 | 
			
		||||
                self.assertEqual(item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestDeliveryView(OrderItemViewTestMixin, WebTestCase):
 | 
			
		||||
 | 
			
		||||
    def make_view(self):
 | 
			
		||||
        return mod.DeliveryView(self.request)
 | 
			
		||||
 | 
			
		||||
    def test_configure_grid(self):
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # nothing added without perms
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
        view.configure_grid(grid)
 | 
			
		||||
        self.assertFalse(grid.checkable)
 | 
			
		||||
        self.assertEqual(len(grid.tools), 0)
 | 
			
		||||
 | 
			
		||||
        # buttons added with perm
 | 
			
		||||
        with patch.object(self.request, 'is_root', new=True):
 | 
			
		||||
            view.configure_grid(grid)
 | 
			
		||||
            self.assertEqual(len(grid.tools), 2)
 | 
			
		||||
            self.assertTrue(grid.checkable)
 | 
			
		||||
 | 
			
		||||
            self.assertIn('process_delivery', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_delivery']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Delivered', tool)
 | 
			
		||||
 | 
			
		||||
            self.assertIn('process_restock', grid.tools)
 | 
			
		||||
            tool = grid.tools['process_restock']
 | 
			
		||||
            self.assertIn('<b-button ', tool)
 | 
			
		||||
            self.assertIn('Restocked', tool)
 | 
			
		||||
 | 
			
		||||
    def test_process_delivery(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_delivery', '/delivery/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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_CONTACTED)
 | 
			
		||||
        order.items.append(item1)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_delivery') as process_delivery:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_delivery)
 | 
			
		||||
                    process_delivery.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # all info provided
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': item1.uuid.hex,
 | 
			
		||||
                        'note': 'extra note',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_delivery()
 | 
			
		||||
                self.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_DELIVERED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 2)
 | 
			
		||||
                self.assertIsNone(item1.events[0].note)
 | 
			
		||||
                self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_DELIVERED)
 | 
			
		||||
                self.assertEqual(item1.events[1].note, "extra note")
 | 
			
		||||
                self.assertEqual(item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
 | 
			
		||||
    def test_process_restock(self):
 | 
			
		||||
        self.pyramid_config.add_route('order_items_delivery', '/delivery/')
 | 
			
		||||
        model = self.app.model
 | 
			
		||||
        enum = self.app.enum
 | 
			
		||||
        view = self.make_view()
 | 
			
		||||
        grid = view.make_grid(model_class=model.OrderItem)
 | 
			
		||||
 | 
			
		||||
        # 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_CONTACTED)
 | 
			
		||||
        order.items.append(item1)
 | 
			
		||||
        self.session.add(order)
 | 
			
		||||
        self.session.flush()
 | 
			
		||||
 | 
			
		||||
        # view only configured for POST
 | 
			
		||||
        with patch.multiple(self.request, method='POST', user=user):
 | 
			
		||||
            with patch.object(view, 'Session', return_value=self.session):
 | 
			
		||||
 | 
			
		||||
                # redirect if items not specified
 | 
			
		||||
                with patch.object(view.order_handler, 'process_restock') as process_restock:
 | 
			
		||||
                    self.assertRaises(HTTPFound, view.process_restock)
 | 
			
		||||
                    process_restock.assert_not_called()
 | 
			
		||||
                    self.assertTrue(self.request.session.pop_flash('warning'))
 | 
			
		||||
                    self.assertFalse(self.request.session.peek_flash())
 | 
			
		||||
 | 
			
		||||
                # all info provided
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 0)
 | 
			
		||||
                with patch.object(self.request, 'POST', new={
 | 
			
		||||
                        'item_uuids': item1.uuid.hex,
 | 
			
		||||
                        'note': 'extra note',
 | 
			
		||||
                }):
 | 
			
		||||
                    view.process_restock()
 | 
			
		||||
                self.assertFalse(self.request.session.peek_flash('warning'))
 | 
			
		||||
                self.assertTrue(self.request.session.pop_flash())
 | 
			
		||||
                self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RESTOCKED)
 | 
			
		||||
                self.assertEqual(len(item1.events), 2)
 | 
			
		||||
                self.assertIsNone(item1.events[0].note)
 | 
			
		||||
                self.assertEqual(item1.events[0].type_code, enum.ORDER_ITEM_EVENT_RESTOCKED)
 | 
			
		||||
                self.assertEqual(item1.events[1].note, "extra note")
 | 
			
		||||
                self.assertEqual(item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue