feat: add initial workflow master views, UI features
This commit is contained in:
parent
9d378a0c5f
commit
7167c6a7cc
|
@ -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…
Reference in a new issue