feat: add tools to change order item status; add notes
This commit is contained in:
parent
b4deea76e0
commit
9d378a0c5f
|
@ -370,8 +370,8 @@ ORDER_ITEM_EVENT = OrderedDict([
|
||||||
(ORDER_ITEM_EVENT_CONTACTED, "customer contacted"),
|
(ORDER_ITEM_EVENT_CONTACTED, "customer contacted"),
|
||||||
(ORDER_ITEM_EVENT_CONTACT_FAILED, "contact failed"),
|
(ORDER_ITEM_EVENT_CONTACT_FAILED, "contact failed"),
|
||||||
(ORDER_ITEM_EVENT_DELIVERED, "delivered"),
|
(ORDER_ITEM_EVENT_DELIVERED, "delivered"),
|
||||||
(ORDER_ITEM_EVENT_STATUS_CHANGE, "status change"),
|
(ORDER_ITEM_EVENT_STATUS_CHANGE, "changed status"),
|
||||||
(ORDER_ITEM_EVENT_NOTE_ADDED, "note added"),
|
(ORDER_ITEM_EVENT_NOTE_ADDED, "added note"),
|
||||||
(ORDER_ITEM_EVENT_CANCELED, "canceled"),
|
(ORDER_ITEM_EVENT_CANCELED, "canceled"),
|
||||||
(ORDER_ITEM_EVENT_REFUND_PENDING, "refund pending"),
|
(ORDER_ITEM_EVENT_REFUND_PENDING, "refund pending"),
|
||||||
(ORDER_ITEM_EVENT_REFUNDED, "refunded"),
|
(ORDER_ITEM_EVENT_REFUNDED, "refunded"),
|
||||||
|
|
|
@ -75,3 +75,24 @@ class OrderHandler(GenericHandler):
|
||||||
unit_qty = self.app.render_quantity(order_qty)
|
unit_qty = self.app.render_quantity(order_qty)
|
||||||
EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT]
|
EA = enum.ORDER_UOM[enum.ORDER_UOM_UNIT]
|
||||||
return f"{unit_qty} {EA}"
|
return f"{unit_qty} {EA}"
|
||||||
|
|
||||||
|
def item_status_to_variant(self, status_code):
|
||||||
|
"""
|
||||||
|
Return a Buefy style variant for the given status code.
|
||||||
|
|
||||||
|
Default logic will return ``None`` for "normal" item status,
|
||||||
|
but may return ``'warning'`` for some (e.g. canceled).
|
||||||
|
|
||||||
|
:param status_code: The status code for an order item.
|
||||||
|
|
||||||
|
:returns: Style variant string (e.g. ``'warning'``) or
|
||||||
|
``None``.
|
||||||
|
"""
|
||||||
|
enum = self.app.enum
|
||||||
|
if status_code in (enum.ORDER_ITEM_STATUS_CANCELED,
|
||||||
|
enum.ORDER_ITEM_STATUS_REFUND_PENDING,
|
||||||
|
enum.ORDER_ITEM_STATUS_REFUNDED,
|
||||||
|
enum.ORDER_ITEM_STATUS_RESTOCKED,
|
||||||
|
enum.ORDER_ITEM_STATUS_EXPIRED,
|
||||||
|
enum.ORDER_ITEM_STATUS_INACTIVE):
|
||||||
|
return 'warning'
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
## -*- coding: utf-8; -*-
|
## -*- coding: utf-8; -*-
|
||||||
<%inherit file="/master/view.mako" />
|
<%inherit file="/master/view.mako" />
|
||||||
|
|
||||||
|
<%def name="content_title()">
|
||||||
|
(${app.enum.ORDER_ITEM_STATUS[item.status_code]})
|
||||||
|
${instance_title}
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="extra_styles()">
|
<%def name="extra_styles()">
|
||||||
${parent.extra_styles()}
|
${parent.extra_styles()}
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.field .field-label .label {
|
nav .field .field-label .label {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +44,86 @@
|
||||||
<span>${app.render_currency(item.paid_amount)}</span>
|
<span>${app.render_currency(item.paid_amount)}</span>
|
||||||
</b-field>
|
</b-field>
|
||||||
<b-field horizontal label="Status">
|
<b-field horizontal label="Status">
|
||||||
<span>${app.enum.ORDER_ITEM_STATUS[item.status_code]}</span>
|
<div style="display: flex; gap: 1rem; align-items: center;">
|
||||||
|
<span
|
||||||
|
% if item_status_variant:
|
||||||
|
class="has-background-${item_status_variant}"
|
||||||
|
% endif
|
||||||
|
% if master.has_perm('change_status'):
|
||||||
|
style="padding: 0.25rem;"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
${app.enum.ORDER_ITEM_STATUS[item.status_code]}
|
||||||
|
</span>
|
||||||
|
% if master.has_perm('change_status'):
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="edit"
|
||||||
|
@click="changeStatusInit()">
|
||||||
|
Change Status
|
||||||
|
</b-button>
|
||||||
|
<${b}-modal
|
||||||
|
% if request.use_oruga:
|
||||||
|
v-model:active="changeStatusShowDialog"
|
||||||
|
% else:
|
||||||
|
:active.sync="changeStatusShowDialog"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
|
||||||
|
<h4 class="block is-size-4">Change Item Status</h4>
|
||||||
|
|
||||||
|
<b-field horizontal label="Current Status">
|
||||||
|
<span>{{ changeStatusCodes[changeStatusOldCode] }}</span>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<b-field horizontal label="New Status"
|
||||||
|
:type="changeStatusNewCode ? null : 'is-danger'">
|
||||||
|
<b-select v-model="changeStatusNewCode">
|
||||||
|
<option v-for="status in changeStatusCodeOptions"
|
||||||
|
:key="status.key"
|
||||||
|
:value="status.key">
|
||||||
|
{{ status.label }}
|
||||||
|
</option>
|
||||||
|
</b-select>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field label="Note">
|
||||||
|
<b-input v-model="changeStatusNote"
|
||||||
|
type="textarea" rows="4" />
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
:disabled="changeStatusSaveDisabled"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="save"
|
||||||
|
@click="changeStatusSave()">
|
||||||
|
{{ changeStatusSubmitting ? "Working, please wait..." : "Update Status" }}
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="changeStatusShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</${b}-modal>
|
||||||
|
|
||||||
|
${h.form(master.get_action_url('change_status', item), ref='changeStatusForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('new_status', **{'v-model': 'changeStatusNewCode'})}
|
||||||
|
## ${h.hidden('uuids', **{':value': 'changeStatusCheckedRows.map((row) => {return row.uuid}).join()'})}
|
||||||
|
${h.hidden('note', **{':value': 'changeStatusNote'})}
|
||||||
|
${h.end_form()}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
</b-field>
|
</b-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -154,7 +238,70 @@
|
||||||
|
|
||||||
<div style="padding: 0 2rem;">
|
<div style="padding: 0 2rem;">
|
||||||
<nav class="panel" style="width: 100%;">
|
<nav class="panel" style="width: 100%;">
|
||||||
<p class="panel-heading">Events</p>
|
<p class="panel-heading"
|
||||||
|
% if master.has_perm('add_note'):
|
||||||
|
style="display: flex; gap: 2rem; align-items: center;"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<span>Events</span>
|
||||||
|
% if master.has_perm('add_note'):
|
||||||
|
<b-button type="is-primary"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="plus"
|
||||||
|
@click="addNoteInit()">
|
||||||
|
Add Note
|
||||||
|
</b-button>
|
||||||
|
<${b}-modal has-modal-card
|
||||||
|
% if request.use_oruga:
|
||||||
|
v-model:active="addNoteShowDialog"
|
||||||
|
% else:
|
||||||
|
:active.sync="addNoteShowDialog"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
<div class="modal-card">
|
||||||
|
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Add Note</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="modal-card-body">
|
||||||
|
<b-field>
|
||||||
|
<b-input type="textarea" rows="8"
|
||||||
|
v-model="addNoteText"
|
||||||
|
ref="addNoteText"
|
||||||
|
expanded />
|
||||||
|
</b-field>
|
||||||
|
## <b-field>
|
||||||
|
## <b-checkbox v-model="addNoteApplyAll">
|
||||||
|
## Apply to all products on this order
|
||||||
|
## </b-checkbox>
|
||||||
|
## </b-field>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<b-button type="is-primary"
|
||||||
|
@click="addNoteSave()"
|
||||||
|
:disabled="addNoteSaveDisabled"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="save">
|
||||||
|
{{ addNoteSubmitting ? "Working, please wait..." : "Add Note" }}
|
||||||
|
</b-button>
|
||||||
|
<b-button @click="addNoteShowDialog = false">
|
||||||
|
Cancel
|
||||||
|
</b-button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</${b}-modal>
|
||||||
|
|
||||||
|
${h.form(master.get_action_url('add_note', item), ref='addNoteForm')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('note', **{':value': 'addNoteText'})}
|
||||||
|
## ${h.hidden('uuids', **{':value': 'changeStatusCheckedRows.map((row) => {return row.uuid}).join()'})}
|
||||||
|
${h.end_form()}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div style="width: 100%;">
|
<div style="width: 100%;">
|
||||||
${events_grid.render_table_element()}
|
${events_grid.render_table_element()}
|
||||||
|
@ -174,6 +321,80 @@
|
||||||
${parent.modify_vue_vars()}
|
${parent.modify_vue_vars()}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
% if master.has_perm('add_note'):
|
||||||
|
|
||||||
|
ThisPageData.addNoteShowDialog = false
|
||||||
|
ThisPageData.addNoteText = null
|
||||||
|
## ThisPageData.addNoteApplyAll = false
|
||||||
|
ThisPageData.addNoteSubmitting = false
|
||||||
|
|
||||||
|
ThisPage.computed.addNoteSaveDisabled = function() {
|
||||||
|
if (!this.addNoteText) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (this.addNoteSubmitting) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.addNoteInit = function() {
|
||||||
|
this.addNoteText = null
|
||||||
|
## this.addNoteApplyAll = false
|
||||||
|
this.addNoteShowDialog = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.addNoteText.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.addNoteSave = function() {
|
||||||
|
this.addNoteSubmitting = true
|
||||||
|
this.$refs.addNoteForm.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if master.has_perm('change_status'):
|
||||||
|
|
||||||
|
ThisPageData.changeStatusCodes = ${json.dumps(app.enum.ORDER_ITEM_STATUS)|n}
|
||||||
|
ThisPageData.changeStatusCodeOptions = ${json.dumps([dict(key=k, label=v) for k, v in app.enum.ORDER_ITEM_STATUS.items()])|n}
|
||||||
|
|
||||||
|
ThisPageData.changeStatusShowDialog = false
|
||||||
|
ThisPageData.changeStatusOldCode = ${instance.status_code}
|
||||||
|
ThisPageData.changeStatusNewCode = null
|
||||||
|
ThisPageData.changeStatusNote = null
|
||||||
|
ThisPageData.changeStatusSubmitting = false
|
||||||
|
|
||||||
|
ThisPage.computed.changeStatusSaveDisabled = function() {
|
||||||
|
if (!this.changeStatusNewCode) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (this.changeStatusSubmitting) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.changeStatusInit = function() {
|
||||||
|
this.changeStatusNewCode = null
|
||||||
|
// clear out any checked rows
|
||||||
|
// this.changeStatusCheckedRows.length = 0
|
||||||
|
this.changeStatusNote = null
|
||||||
|
this.changeStatusShowDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ThisPage.methods.changeStatusSave = function() {
|
||||||
|
if (this.changeStatusNewCode == this.changeStatusOldCode) {
|
||||||
|
alert("You chose the same status it already had...")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.changeStatusSubmitting = true
|
||||||
|
this.$refs.changeStatusForm.submit()
|
||||||
|
}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
## TODO: ugh the hackiness
|
## TODO: ugh the hackiness
|
||||||
ThisPageData.gridContext = {
|
ThisPageData.gridContext = {
|
||||||
% for key, data in form.grid_vue_context.items():
|
% for key, data in form.grid_vue_context.items():
|
||||||
|
|
|
@ -30,7 +30,7 @@ import logging
|
||||||
import colander
|
import colander
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
|
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags, HTML
|
||||||
|
|
||||||
from wuttaweb.views import MasterView
|
from wuttaweb.views import MasterView
|
||||||
from wuttaweb.forms.schema import UserRef, WuttaMoney, WuttaQuantity, WuttaEnum, WuttaDictEnum
|
from wuttaweb.forms.schema import UserRef, WuttaMoney, WuttaQuantity, WuttaEnum, WuttaDictEnum
|
||||||
|
@ -862,6 +862,15 @@ class OrderView(MasterView):
|
||||||
# status_code
|
# status_code
|
||||||
g.set_renderer('status_code', self.render_status_code)
|
g.set_renderer('status_code', self.render_status_code)
|
||||||
|
|
||||||
|
# TODO: upstream should set this automatically
|
||||||
|
g.row_class = self.row_grid_row_class
|
||||||
|
|
||||||
|
def row_grid_row_class(self, item, data, i):
|
||||||
|
""" """
|
||||||
|
variant = self.order_handler.item_status_to_variant(item.status_code)
|
||||||
|
if variant:
|
||||||
|
return f'has-background-{variant}'
|
||||||
|
|
||||||
def render_status_code(self, item, key, value):
|
def render_status_code(self, item, key, value):
|
||||||
""" """
|
""" """
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
|
@ -1117,12 +1126,11 @@ class OrderItemView(MasterView):
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
return enum.ORDER_ITEM_STATUS[value]
|
return enum.ORDER_ITEM_STATUS[value]
|
||||||
|
|
||||||
def get_instance_title(self, item):
|
def grid_row_class(self, item, data, i):
|
||||||
""" """
|
""" """
|
||||||
enum = self.app.enum
|
variant = self.order_handler.item_status_to_variant(item.status_code)
|
||||||
title = str(item)
|
if variant:
|
||||||
status = enum.ORDER_ITEM_STATUS[item.status_code]
|
return f'has-background-{variant}'
|
||||||
return f"({status}) {title}"
|
|
||||||
|
|
||||||
def configure_form(self, f):
|
def configure_form(self, f):
|
||||||
""" """
|
""" """
|
||||||
|
@ -1185,6 +1193,7 @@ class OrderItemView(MasterView):
|
||||||
context['order'] = item.order
|
context['order'] = item.order
|
||||||
context['order_qty_uom_text'] = self.order_handler.get_order_qty_uom_text(
|
context['order_qty_uom_text'] = self.order_handler.get_order_qty_uom_text(
|
||||||
item.order_qty, item.order_uom, case_size=item.case_size, html=True)
|
item.order_qty, item.order_uom, case_size=item.case_size, html=True)
|
||||||
|
context['item_status_variant'] = self.order_handler.item_status_to_variant(item.status_code)
|
||||||
|
|
||||||
grid = self.make_grid(key=f'{route_prefix}.view.events',
|
grid = self.make_grid(key=f'{route_prefix}.view.events',
|
||||||
model_class=model.OrderItemEvent,
|
model_class=model.OrderItemEvent,
|
||||||
|
@ -1201,6 +1210,7 @@ class OrderItemView(MasterView):
|
||||||
'type_code': "Event Type",
|
'type_code': "Event Type",
|
||||||
})
|
})
|
||||||
grid.set_renderer('type_code', lambda e, k, v: enum.ORDER_ITEM_EVENT[v])
|
grid.set_renderer('type_code', lambda e, k, v: enum.ORDER_ITEM_EVENT[v])
|
||||||
|
grid.set_renderer('note', self.render_event_note)
|
||||||
if self.request.has_perm('users.view'):
|
if self.request.has_perm('users.view'):
|
||||||
grid.set_renderer('actor', lambda e, k, v: tags.link_to(
|
grid.set_renderer('actor', lambda e, k, v: tags.link_to(
|
||||||
e.actor, self.request.route_url('users.view', uuid=e.actor.uuid)))
|
e.actor, self.request.route_url('users.view', uuid=e.actor.uuid)))
|
||||||
|
@ -1209,6 +1219,15 @@ class OrderItemView(MasterView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def render_event_note(self, event, key, value):
|
||||||
|
""" """
|
||||||
|
enum = self.app.enum
|
||||||
|
if event.type_code == enum.ORDER_ITEM_EVENT_NOTE_ADDED:
|
||||||
|
return HTML.tag('span', class_='has-background-info-light',
|
||||||
|
style='padding: 0.25rem 0.5rem;',
|
||||||
|
c=[value])
|
||||||
|
return value
|
||||||
|
|
||||||
def get_xref_buttons(self, item):
|
def get_xref_buttons(self, item):
|
||||||
""" """
|
""" """
|
||||||
buttons = super().get_xref_buttons(item)
|
buttons = super().get_xref_buttons(item)
|
||||||
|
@ -1221,6 +1240,112 @@ class OrderItemView(MasterView):
|
||||||
|
|
||||||
return buttons
|
return buttons
|
||||||
|
|
||||||
|
def add_note(self):
|
||||||
|
"""
|
||||||
|
View which adds a note to an order item. This is POST-only;
|
||||||
|
will redirect back to the item view.
|
||||||
|
"""
|
||||||
|
enum = self.app.enum
|
||||||
|
item = self.get_instance()
|
||||||
|
|
||||||
|
item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED, self.request.user,
|
||||||
|
note=self.request.POST['note'])
|
||||||
|
|
||||||
|
return self.redirect(self.get_action_url('view', item))
|
||||||
|
|
||||||
|
def change_status(self):
|
||||||
|
"""
|
||||||
|
View which changes status for an order item. This is
|
||||||
|
POST-only; will redirect back to the item view.
|
||||||
|
"""
|
||||||
|
model = self.app.model
|
||||||
|
enum = self.app.enum
|
||||||
|
main_item = self.get_instance()
|
||||||
|
session = self.Session()
|
||||||
|
redirect = self.redirect(self.get_action_url('view', main_item))
|
||||||
|
|
||||||
|
extra_note = self.request.POST.get('note')
|
||||||
|
|
||||||
|
# validate new status
|
||||||
|
new_status_code = int(self.request.POST['new_status'])
|
||||||
|
if new_status_code not in enum.ORDER_ITEM_STATUS:
|
||||||
|
self.request.session.flash("Invalid status code", 'error')
|
||||||
|
return redirect
|
||||||
|
new_status_text = enum.ORDER_ITEM_STATUS[new_status_code]
|
||||||
|
|
||||||
|
# locate all items to which new status will be applied
|
||||||
|
items = [main_item]
|
||||||
|
# uuids = self.request.POST.get('uuids')
|
||||||
|
# if uuids:
|
||||||
|
# for uuid in uuids.split(','):
|
||||||
|
# item = Session.get(model.OrderItem, uuid)
|
||||||
|
# if item:
|
||||||
|
# items.append(item)
|
||||||
|
|
||||||
|
# update item(s)
|
||||||
|
for item in items:
|
||||||
|
if item.status_code != new_status_code:
|
||||||
|
|
||||||
|
# event: change status
|
||||||
|
note = 'status changed from "{}" to "{}"'.format(
|
||||||
|
enum.ORDER_ITEM_STATUS[item.status_code],
|
||||||
|
new_status_text)
|
||||||
|
item.add_event(enum.ORDER_ITEM_EVENT_STATUS_CHANGE,
|
||||||
|
self.request.user, note=note)
|
||||||
|
|
||||||
|
# event: add note
|
||||||
|
if extra_note:
|
||||||
|
item.add_event(enum.ORDER_ITEM_EVENT_NOTE_ADDED,
|
||||||
|
self.request.user, note=extra_note)
|
||||||
|
|
||||||
|
# new status
|
||||||
|
item.status_code = new_status_code
|
||||||
|
|
||||||
|
self.request.session.flash(f"Status has been updated to: {new_status_text}")
|
||||||
|
return redirect
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
cls._order_item_defaults(config)
|
||||||
|
cls._defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _order_item_defaults(cls, config):
|
||||||
|
route_prefix = cls.get_route_prefix()
|
||||||
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
instance_url_prefix = cls.get_instance_url_prefix()
|
||||||
|
model_title = cls.get_model_title()
|
||||||
|
model_title_plural = cls.get_model_title_plural()
|
||||||
|
|
||||||
|
# fix perm group
|
||||||
|
config.add_wutta_permission_group(permission_prefix,
|
||||||
|
model_title_plural,
|
||||||
|
overwrite=False)
|
||||||
|
|
||||||
|
# add note
|
||||||
|
config.add_route(f'{route_prefix}.add_note',
|
||||||
|
f'{instance_url_prefix}/add_note',
|
||||||
|
request_method='POST')
|
||||||
|
config.add_view(cls, attr='add_note',
|
||||||
|
route_name=f'{route_prefix}.add_note',
|
||||||
|
renderer='json',
|
||||||
|
permission=f'{permission_prefix}.add_note')
|
||||||
|
config.add_wutta_permission(permission_prefix,
|
||||||
|
f'{permission_prefix}.add_note',
|
||||||
|
f"Add note for {model_title}")
|
||||||
|
|
||||||
|
# change status
|
||||||
|
config.add_route(f'{route_prefix}.change_status',
|
||||||
|
f'{instance_url_prefix}/change-status',
|
||||||
|
request_method='POST')
|
||||||
|
config.add_view(cls, attr='change_status',
|
||||||
|
route_name=f'{route_prefix}.change_status',
|
||||||
|
renderer='json',
|
||||||
|
permission=f'{permission_prefix}.change_status')
|
||||||
|
config.add_wutta_permission(permission_prefix,
|
||||||
|
f'{permission_prefix}.change_status',
|
||||||
|
f"Change status for {model_title}")
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
@ -38,3 +38,23 @@ class TestOrderHandler(DataTestCase):
|
||||||
self.assertEqual(text, "2 Units")
|
self.assertEqual(text, "2 Units")
|
||||||
text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_UNIT, html=True)
|
text = handler.get_order_qty_uom_text(2, enum.ORDER_UOM_UNIT, html=True)
|
||||||
self.assertEqual(text, "2 Units")
|
self.assertEqual(text, "2 Units")
|
||||||
|
|
||||||
|
def test_item_status_to_variant(self):
|
||||||
|
enum = self.app.enum
|
||||||
|
handler = self.make_handler()
|
||||||
|
|
||||||
|
# typical
|
||||||
|
self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_INITIATED))
|
||||||
|
self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_READY))
|
||||||
|
self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_PLACED))
|
||||||
|
self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_RECEIVED))
|
||||||
|
self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_CONTACTED))
|
||||||
|
self.assertIsNone(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_PAID))
|
||||||
|
|
||||||
|
# warning
|
||||||
|
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_CANCELED), 'warning')
|
||||||
|
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_REFUND_PENDING), 'warning')
|
||||||
|
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_REFUNDED), 'warning')
|
||||||
|
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_RESTOCKED), 'warning')
|
||||||
|
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_EXPIRED), 'warning')
|
||||||
|
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_INACTIVE), 'warning')
|
||||||
|
|
|
@ -1183,6 +1183,19 @@ class TestOrderView(WebTestCase):
|
||||||
view.configure_row_grid(grid)
|
view.configure_row_grid(grid)
|
||||||
self.assertIn('product_scancode', grid.linked_columns)
|
self.assertIn('product_scancode', grid.linked_columns)
|
||||||
|
|
||||||
|
def test_row_grid_row_class(self):
|
||||||
|
model = self.app.model
|
||||||
|
enum = self.app.enum
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# typical
|
||||||
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_READY)
|
||||||
|
self.assertIsNone(view.row_grid_row_class(item, {}, 1))
|
||||||
|
|
||||||
|
# warning
|
||||||
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_CANCELED)
|
||||||
|
self.assertEqual(view.row_grid_row_class(item, {}, 1), 'has-background-warning')
|
||||||
|
|
||||||
def test_render_status_code(self):
|
def test_render_status_code(self):
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
|
@ -1291,17 +1304,18 @@ class TestOrderItemView(WebTestCase):
|
||||||
self.assertEqual(view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED),
|
self.assertEqual(view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED),
|
||||||
'initiated')
|
'initiated')
|
||||||
|
|
||||||
def test_get_instance_title(self):
|
def test_grid_row_class(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
enum = self.app.enum
|
enum = self.app.enum
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
|
|
||||||
item = model.OrderItem(product_brand='Bragg',
|
# typical
|
||||||
product_description='Vinegar',
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_READY)
|
||||||
product_size='32oz',
|
self.assertIsNone(view.grid_row_class(item, {}, 1))
|
||||||
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
|
|
||||||
title = view.get_instance_title(item)
|
# warning
|
||||||
self.assertEqual(title, "(initiated) Bragg Vinegar 32oz")
|
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_configure_form(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
@ -1349,6 +1363,24 @@ class TestOrderItemView(WebTestCase):
|
||||||
self.assertIn('order_qty_uom_text', context)
|
self.assertIn('order_qty_uom_text', context)
|
||||||
self.assertEqual(context['order_qty_uom_text'], "2 Cases (× 8 = 16 Units)")
|
self.assertEqual(context['order_qty_uom_text'], "2 Cases (× 8 = 16 Units)")
|
||||||
|
|
||||||
|
def test_render_event_note(self):
|
||||||
|
model = self.app.model
|
||||||
|
enum = self.app.enum
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# typical
|
||||||
|
event = model.OrderItemEvent(type_code=enum.ORDER_ITEM_EVENT_READY, note='testing')
|
||||||
|
result = view.render_event_note(event, 'note', 'testing')
|
||||||
|
self.assertEqual(result, 'testing')
|
||||||
|
|
||||||
|
# user note
|
||||||
|
event = model.OrderItemEvent(type_code=enum.ORDER_ITEM_EVENT_NOTE_ADDED, note='testing2')
|
||||||
|
result = view.render_event_note(event, 'note', 'testing2')
|
||||||
|
self.assertNotEqual(result, 'testing2')
|
||||||
|
self.assertIn('<span', result)
|
||||||
|
self.assertIn('class="has-background-info-light"', result)
|
||||||
|
self.assertIn('testing2', result)
|
||||||
|
|
||||||
def test_get_xref_buttons(self):
|
def test_get_xref_buttons(self):
|
||||||
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
|
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
@ -1371,3 +1403,86 @@ class TestOrderItemView(WebTestCase):
|
||||||
buttons = view.get_xref_buttons(item)
|
buttons = view.get_xref_buttons(item)
|
||||||
self.assertEqual(len(buttons), 1)
|
self.assertEqual(len(buttons), 1)
|
||||||
self.assertIn("View the Order", buttons[0])
|
self.assertIn("View the Order", buttons[0])
|
||||||
|
|
||||||
|
def test_add_note(self):
|
||||||
|
self.pyramid_config.add_route('order_items.view', '/order-items/{uuid}')
|
||||||
|
model = self.app.model
|
||||||
|
enum = self.app.enum
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
user = model.User(username='barney')
|
||||||
|
self.session.add(user)
|
||||||
|
|
||||||
|
order = model.Order(order_id=42, created_by=user)
|
||||||
|
self.session.add(order)
|
||||||
|
item = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
|
||||||
|
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
|
||||||
|
order.items.append(item)
|
||||||
|
self.session.flush()
|
||||||
|
|
||||||
|
with patch.object(view, 'Session', return_value=self.session):
|
||||||
|
with patch.object(self.request, 'matchdict', new={'uuid': item.uuid}):
|
||||||
|
with patch.object(self.request, 'POST', new={'note': 'testing'}):
|
||||||
|
self.assertEqual(len(item.events), 0)
|
||||||
|
result = view.add_note()
|
||||||
|
self.assertEqual(len(item.events), 1)
|
||||||
|
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}')
|
||||||
|
model = self.app.model
|
||||||
|
enum = self.app.enum
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
user = model.User(username='barney')
|
||||||
|
self.session.add(user)
|
||||||
|
|
||||||
|
order = model.Order(order_id=42, created_by=user)
|
||||||
|
self.session.add(order)
|
||||||
|
item = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
|
||||||
|
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
|
||||||
|
order.items.append(item)
|
||||||
|
self.session.flush()
|
||||||
|
|
||||||
|
with patch.object(view, 'Session', return_value=self.session):
|
||||||
|
with patch.object(self.request, 'user', new=user):
|
||||||
|
with patch.object(self.request, 'matchdict', new={'uuid': item.uuid}):
|
||||||
|
|
||||||
|
# just status change, no note
|
||||||
|
with patch.object(self.request, 'POST', new={
|
||||||
|
'new_status': enum.ORDER_ITEM_STATUS_PLACED}):
|
||||||
|
self.assertEqual(len(item.events), 0)
|
||||||
|
result = view.change_status()
|
||||||
|
self.assertIsInstance(result, HTTPFound)
|
||||||
|
self.assertFalse(self.request.session.peek_flash('error'))
|
||||||
|
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 change plus note
|
||||||
|
with patch.object(self.request, 'POST', new={
|
||||||
|
'new_status': enum.ORDER_ITEM_STATUS_RECEIVED,
|
||||||
|
'note': 'check it out'}):
|
||||||
|
self.assertEqual(len(item.events), 1)
|
||||||
|
result = view.change_status()
|
||||||
|
self.assertIsInstance(result, HTTPFound)
|
||||||
|
self.assertFalse(self.request.session.peek_flash('error'))
|
||||||
|
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'")
|
||||||
|
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'")
|
||||||
|
self.assertEqual(item.events[2].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
|
||||||
|
self.assertEqual(item.events[2].note, "check it out")
|
||||||
|
|
||||||
|
# invalid status
|
||||||
|
with patch.object(self.request, 'POST', new={'new_status': 23432143}):
|
||||||
|
self.assertEqual(len(item.events), 3)
|
||||||
|
result = view.change_status()
|
||||||
|
self.assertIsInstance(result, HTTPFound)
|
||||||
|
self.assertTrue(self.request.session.peek_flash('error'))
|
||||||
|
self.assertEqual(len(item.events), 3)
|
||||||
|
|
Loading…
Reference in a new issue