feat: add tools to change order item status; add notes

This commit is contained in:
Lance Edgar 2025-01-15 21:49:17 -06:00
parent b4deea76e0
commit 9d378a0c5f
6 changed files with 520 additions and 18 deletions

View file

@ -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"),

View file

@ -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'

View file

@ -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():

View file

@ -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()

View file

@ -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')

View file

@ -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 (&times; 8 = 16 Units)") self.assertEqual(context['order_qty_uom_text'], "2 Cases (&times; 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)