Allow changing status, adding notes for customer order items

This commit is contained in:
Lance Edgar 2021-09-27 13:25:02 -04:00
parent 7c6c2f7ded
commit ab517d1199
3 changed files with 555 additions and 12 deletions

View file

@ -34,7 +34,7 @@ from sqlalchemy import orm
from rattail.db import model
from rattail.time import localtime
from webhelpers2.html import tags
from webhelpers2.html import HTML, tags
from tailbone.views import MasterView
from tailbone.util import raw_datetime
@ -54,6 +54,7 @@ class CustomerOrderItemView(MasterView):
labels = {
'order_id': "Order ID",
'order_uom': "Order UOM",
'status_code': "Status",
}
grid_columns = [
@ -99,6 +100,7 @@ class CustomerOrderItemView(MasterView):
'total_price',
'paid_amount',
'status_code',
'notes',
]
def query(self, session):
@ -139,7 +141,6 @@ class CustomerOrderItemView(MasterView):
g.set_label('product_brand', "Brand")
g.set_label('product_description', "Description")
g.set_label('product_size', "Size")
g.set_label('status_code', "Status")
g.set_link('order_id')
g.set_link('person')
@ -161,6 +162,7 @@ class CustomerOrderItemView(MasterView):
def configure_form(self, f):
super(CustomerOrderItemView, self).configure_form(f)
use_buefy = self.get_use_buefy()
# order
f.set_renderer('order', self.render_order)
@ -187,10 +189,193 @@ class CustomerOrderItemView(MasterView):
# person
f.set_renderer('person', self.render_person)
f.set_enum('status_code', self.enum.CUSTORDER_ITEM_STATUS)
# status_code
f.set_renderer('status_code', self.render_status_code)
# label overrides
f.set_label('status_code', "Status")
# notes
if use_buefy:
f.set_renderer('notes', self.render_notes)
else:
f.remove('notes')
def render_status_code(self, item, field):
use_buefy = self.get_use_buefy()
text = self.enum.CUSTORDER_ITEM_STATUS[item.status_code]
items = [HTML.tag('span', c=[text])]
if use_buefy and self.has_perm('change_status'):
button = HTML.tag('b-button', type='is-primary', c="Change Status",
style='margin-left: 1rem;',
icon_pack='fas', icon_left='edit',
**{'@click': "$emit('change-status')"})
items.append(button)
left = HTML.tag('div', class_='level-left', c=items)
outer = HTML.tag('div', class_='level', c=[left])
return outer
def render_notes(self, item, field):
route_prefix = self.get_route_prefix()
factory = self.get_grid_factory()
g = factory(
key='{}.notes'.format(route_prefix),
data=[],
columns=[
'text',
'created_by',
'created',
],
labels={
'text': "Note",
},
)
table = HTML.literal(
g.render_buefy_table_element(data_prop='notesData'))
elements = [table]
if self.has_perm('add_note'):
button = HTML.tag('b-button', type='is-primary', c="Add Note",
class_='is-pulled-right',
icon_pack='fas', icon_left='plus',
**{'@click': "$emit('add-note')"})
button_wrapper = HTML.tag('div', c=[button],
style='margin-top: 0.5rem;')
elements.append(button_wrapper)
return HTML.tag('div',
style='display: flex; flex-direction: column;',
c=elements)
def template_kwargs_view(self, **kwargs):
kwargs = super(CustomerOrderItemView, self).template_kwargs_view(**kwargs)
app = self.get_rattail_app()
item = kwargs['instance']
# fetch notes for current item
kwargs['notes_data'] = self.get_context_notes(item)
# fetch "other" order items, siblings of current one
order = item.order
other_items = self.Session.query(model.CustomerOrderItem)\
.filter(model.CustomerOrderItem.order == order)\
.filter(model.CustomerOrderItem.uuid != item.uuid)\
.all()
other_data = []
for other in other_items:
order_date = None
if order.created:
order_date = localtime(self.rattail_config, order.created, from_utc=True).date()
other_data.append({
'uuid': other.uuid,
'brand_name': other.product_brand,
'product_description': other.product_description,
'product_case_quantity': app.render_quantity(other.case_quantity),
'order_quantity': app.render_quantity(other.order_quantity),
'order_uom': self.enum.UNIT_OF_MEASURE[other.order_uom],
'department_name': other.department_name,
'product_barcode': other.product_upc.pretty() if other.product_upc else None,
'unit_price': app.render_currency(other.unit_price),
'total_price': app.render_currency(other.total_price),
'order_date': app.render_date(order_date),
'status_code': self.enum.CUSTORDER_ITEM_STATUS[other.status_code],
})
kwargs['other_order_items_data'] = other_data
return kwargs
def get_context_notes(self, item):
notes = []
for note in reversed(item.notes):
created = localtime(self.rattail_config, note.created, from_utc=True)
notes.append({
'created': raw_datetime(self.rattail_config, created),
'created_by': note.created_by.display_name,
'text': note.text,
})
return notes
def change_status(self):
"""
View for changing status of one or more order items.
"""
order_item = self.get_instance()
redirect = self.redirect(self.get_action_url('view', order_item))
# validate new status
new_status_code = int(self.request.POST['new_status_code'])
if new_status_code not in self.enum.CUSTORDER_ITEM_STATUS:
self.request.session.flash("Invalid status code", 'error')
return redirect
# locate order items to which new status will be applied
order_items = [order_item]
uuids = self.request.POST['uuids']
if uuids:
for uuid in uuids.split(','):
item = self.Session.query(model.CustomerOrderItem).get(uuid)
if item:
order_items.append(item)
# locate user responsible for change
user = self.request.user
# maybe grab extra user-provided note to attach
extra_note = self.request.POST.get('note')
# apply new status to order item(s)
for item in order_items:
if item.status_code != new_status_code:
# attach event
note = "status changed from \"{}\" to \"{}\"".format(
self.enum.CUSTORDER_ITEM_STATUS[item.status_code],
self.enum.CUSTORDER_ITEM_STATUS[new_status_code])
if extra_note:
note = "{} - NOTE: {}".format(note, extra_note)
item.events.append(model.CustomerOrderItemEvent(
type_code=self.enum.CUSTORDER_ITEM_EVENT_STATUS_CHANGE,
user=user, note=note))
# change status
item.status_code = new_status_code
self.request.session.flash("Status has been updated to: {}".format(
self.enum.CUSTORDER_ITEM_STATUS[new_status_code]))
return redirect
def add_note(self):
"""
View for adding a new note to current order item, optinally
also adding it to all other items under the parent order.
"""
order_item = self.get_instance()
data = self.request.json_body
new_note = data['note']
apply_all = data['apply_all'] == True
user = self.request.user
if apply_all:
order_items = order_item.order.items
else:
order_items = [order_item]
for item in order_items:
item.notes.append(model.CustomerOrderItemNote(
created_by=user, text=new_note))
# # attach event
# item.events.append(model.CustomerOrderItemEvent(
# type_code=self.enum.CUSTORDER_ITEM_EVENT_ADDED_NOTE,
# user=user, note=new_note))
self.Session.flush()
self.Session.refresh(order_item)
return {'success': True,
'notes': self.get_context_notes(order_item)}
def render_order(self, item, field):
order = item.order
@ -210,16 +395,58 @@ class CustomerOrderItemView(MasterView):
def get_row_data(self, item):
return self.Session.query(model.CustomerOrderItemEvent)\
.filter(model.CustomerOrderItemEvent.item == item)\
.order_by(model.CustomerOrderItemEvent.occurred,
.order_by(model.CustomerOrderItemEvent.occurred.desc(),
model.CustomerOrderItemEvent.type_code)
def configure_row_grid(self, g):
super(CustomerOrderItemView, self).configure_row_grid(g)
g.set_enum('type_code', self.enum.CUSTORDER_ITEM_EVENT)
g.set_label('occurred', "When")
g.set_label('type_code', "What") # TODO: enum renderer
g.set_label('user', "Who")
g.set_label('note', "Notes")
@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()
instance_url_prefix = cls.get_instance_url_prefix()
permission_prefix = cls.get_permission_prefix()
model_title_plural = cls.get_model_title_plural()
# fix permission group name
config.add_tailbone_permission_group(permission_prefix, model_title_plural)
# change status
config.add_tailbone_permission(permission_prefix,
'{}.change_status'.format(permission_prefix),
"Change status for 1 or more {}".format(model_title_plural))
config.add_route('{}.change_status'.format(route_prefix),
'{}/change-status'.format(instance_url_prefix),
request_method='POST')
config.add_view(cls, attr='change_status',
route_name='{}.change_status'.format(route_prefix),
permission='{}.change_status'.format(permission_prefix))
# add note
config.add_tailbone_permission(permission_prefix,
'{}.add_note'.format(permission_prefix),
"Add arbitrary notes for {}".format(model_title_plural))
config.add_route('{}.add_note'.format(route_prefix),
'{}/add-note'.format(instance_url_prefix),
request_method='POST')
config.add_view(cls, attr='add_note',
route_name='{}.add_note'.format(route_prefix),
renderer='json',
permission='{}.add_note'.format(permission_prefix))
# TODO: deprecate / remove this
CustomerOrderItemsView = CustomerOrderItemView

View file

@ -50,6 +50,11 @@ class CustomerOrderView(MasterView):
route_prefix = 'custorders'
editable = False
labels = {
'id': "ID",
'status_code': "Status",
}
grid_columns = [
'id',
'customer',
@ -117,10 +122,6 @@ class CustomerOrderView(MasterView):
g.set_sort_defaults('created', 'desc')
# TODO: enum choices renderer
g.set_label('status_code', "Status")
g.set_label('id', "ID")
g.set_link('id')
g.set_link('customer')
g.set_link('person')
@ -129,7 +130,6 @@ class CustomerOrderView(MasterView):
super(CustomerOrderView, self).configure_form(f)
f.set_readonly('id')
f.set_label('id', "ID")
f.set_renderer('store', self.render_store)
f.set_renderer('customer', self.render_customer)
@ -138,7 +138,6 @@ class CustomerOrderView(MasterView):
f.set_type('total_price', 'currency')
f.set_enum('status_code', self.enum.CUSTORDER_STATUS)
f.set_label('status_code', "Status")
f.set_readonly('created')