Show events instead of notes, in field subgrid for custorder item

This commit is contained in:
Lance Edgar 2023-09-12 12:35:53 -05:00
parent e930199f83
commit 1cad8b2481
2 changed files with 77 additions and 123 deletions

View file

@ -280,7 +280,7 @@
:disabled="addNoteSaveDisabled" :disabled="addNoteSaveDisabled"
icon-pack="fas" icon-pack="fas"
icon-left="save"> icon-left="save">
{{ addNoteSubmitText }} {{ addNoteSubmitting ? "Working, please wait..." : "Save Note" }}
</b-button> </b-button>
<b-button @click="showAddNoteDialog = false"> <b-button @click="showAddNoteDialog = false">
Cancel Cancel
@ -295,7 +295,7 @@
${parent.modify_this_page_vars()} ${parent.modify_this_page_vars()}
<script type="text/javascript"> <script type="text/javascript">
${form.component_studly}Data.notesData = ${json.dumps(notes_data)|n} ${form.component_studly}Data.eventsData = ${json.dumps(events_data)|n}
% if master.has_perm('confirm_price'): % if master.has_perm('confirm_price'):
@ -406,7 +406,6 @@
ThisPageData.newNoteText = null ThisPageData.newNoteText = null
ThisPageData.newNoteApplyAll = false ThisPageData.newNoteApplyAll = false
ThisPageData.addNoteSubmitting = false ThisPageData.addNoteSubmitting = false
ThisPageData.addNoteSubmitText = "Save Note"
ThisPage.computed.addNoteSaveDisabled = function() { ThisPage.computed.addNoteSaveDisabled = function() {
if (!this.newNoteText) { if (!this.newNoteText) {
@ -429,43 +428,19 @@
ThisPage.methods.addNoteSave = function() { ThisPage.methods.addNoteSave = function() {
this.addNoteSubmitting = true this.addNoteSubmitting = true
this.addNoteSubmitText = "Working, please wait..."
let url = '${url('{}.add_note'.format(route_prefix), uuid=instance.uuid)}' let url = '${url('{}.add_note'.format(route_prefix), uuid=instance.uuid)}'
let params = { let params = {
note: this.newNoteText, note: this.newNoteText,
apply_all: this.newNoteApplyAll, apply_all: this.newNoteApplyAll,
} }
let headers = { this.simplePOST(url, params, response => {
## TODO: should find a better way to handle CSRF token this.$refs.mainForm.eventsData = response.data.events
'X-CSRF-TOKEN': this.csrftoken, this.showAddNoteDialog = false
}
## TODO: should find a better way to handle CSRF token
this.$http.post(url, params, {headers: headers}).then(({ data }) => {
if (data.success) {
this.$refs.mainForm.notesData = data.notes
this.showAddNoteDialog = false
} else {
this.$buefy.toast.open({
message: "Save failed: " + (data.error || "(unknown error)"),
type: 'is-danger',
duration: 4000, // 4 seconds
})
}
this.addNoteSubmitting = false this.addNoteSubmitting = false
this.addNoteSubmitText = "Save Note" }, response => {
}).catch((error) => {
// TODO: should handle this better somehow..?
this.$buefy.toast.open({
message: "Save failed: (unknown error)",
type: 'is-danger',
duration: 4000, // 4 seconds
})
this.addNoteSubmitting = false this.addNoteSubmitting = false
this.addNoteSubmitText = "Save Note"
}) })
} }

View file

@ -28,8 +28,7 @@ import datetime
from sqlalchemy import orm from sqlalchemy import orm
from rattail.db import model from rattail.db.model import CustomerOrderItem
from rattail.time import localtime
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
@ -41,7 +40,7 @@ class CustomerOrderItemView(MasterView):
""" """
Master view for customer order items Master view for customer order items
""" """
model_class = model.CustomerOrderItem model_class = CustomerOrderItem
route_prefix = 'custorders.items' route_prefix = 'custorders.items'
url_prefix = '/custorders/items' url_prefix = '/custorders/items'
creatable = False creatable = False
@ -72,21 +71,6 @@ class CustomerOrderItemView(MasterView):
'flagged', 'flagged',
] ]
has_rows = True
model_row_class = model.CustomerOrderItemEvent
rows_title = "Event History"
rows_filterable = False
rows_sortable = False
rows_pageable = False
rows_viewable = False
row_grid_columns = [
'occurred',
'type_code',
'user',
'note',
]
form_fields = [ form_fields = [
'order', 'order',
'customer', 'customer',
@ -98,20 +82,32 @@ class CustomerOrderItemView(MasterView):
'product_brand', 'product_brand',
'product_description', 'product_description',
'product_size', 'product_size',
'case_quantity',
'order_quantity', 'order_quantity',
'order_uom', 'order_uom',
'case_quantity',
'unit_price', 'unit_price',
'total_price', 'total_price',
'special_order', 'special_order',
'price_needs_confirmation', 'price_needs_confirmation',
'paid_amount', 'paid_amount',
'payment_transaction_number',
'status_code', 'status_code',
'flagged', 'flagged',
'notes', 'contact_attempts',
'last_contacted',
'events',
] ]
def __init__(self, request):
super().__init__(request)
app = self.get_rattail_app()
self.custorder_handler = app.get_custorder_handler()
self.batch_handler = app.get_batch_handler(
'custorder',
default='rattail.batch.custorder:CustomerOrderBatchHandler')
def query(self, session): def query(self, session):
model = self.model
return session.query(model.CustomerOrderItem)\ return session.query(model.CustomerOrderItem)\
.join(model.CustomerOrder)\ .join(model.CustomerOrder)\
.options(orm.joinedload(model.CustomerOrderItem.order)\ .options(orm.joinedload(model.CustomerOrderItem.order)\
@ -119,7 +115,7 @@ class CustomerOrderItemView(MasterView):
def configure_grid(self, g): def configure_grid(self, g):
super().configure_grid(g) super().configure_grid(g)
batch_handler = self.get_batch_handler() model = self.model
# order_id # order_id
g.set_renderer('order_id', self.render_order_id) g.set_renderer('order_id', self.render_order_id)
@ -155,7 +151,7 @@ class CustomerOrderItemView(MasterView):
# order_uom # order_uom
# nb. this is not relevant if "case orders only" # nb. this is not relevant if "case orders only"
if not batch_handler.allow_unit_orders(): if not self.batch_handler.allow_unit_orders():
g.remove('order_uom') g.remove('order_uom')
else: else:
g.set_enum('order_uom', self.enum.UNIT_OF_MEASURE) g.set_enum('order_uom', self.enum.UNIT_OF_MEASURE)
@ -168,6 +164,19 @@ class CustomerOrderItemView(MasterView):
# status_code # status_code
g.set_renderer('status_code', self.render_status_code_column) g.set_renderer('status_code', self.render_status_code_column)
# abbreviate some labels, only in grid header
g.set_label('case_quantity', "Case Qty")
g.filters['case_quantity'].label = "Case Quantity"
g.set_label('order_quantity', "Order Qty")
g.filters['order_quantity'].label = "Order Quantity"
g.set_label('department_name', "Department")
g.filters['department_name'].label = "Department Name"
g.set_label('total_price', "Total")
g.filters['total_price'].label = "Total Price"
g.set_label('order_created', "Ordered")
if 'order_created' in g.filters:
g.filters['order_created'].label = "Order Created"
def render_order_id(self, item, field): def render_order_id(self, item, field):
return item.order.id return item.order.id
@ -178,7 +187,8 @@ class CustomerOrderItemView(MasterView):
return text return text
def render_order_created(self, item, column): def render_order_created(self, item, column):
value = localtime(self.rattail_config, item.order.created, from_utc=True) app = self.get_rattail_app()
value = app.localtime(item.order.created, from_utc=True)
return raw_datetime(self.rattail_config, value) return raw_datetime(self.rattail_config, value)
def render_status_code_column(self, item, field): def render_status_code_column(self, item, field):
@ -188,12 +198,6 @@ class CustomerOrderItemView(MasterView):
return HTML.tag('span', title=item.status_text, c=[text]) return HTML.tag('span', title=item.status_text, c=[text])
return text return text
def get_batch_handler(self):
app = self.get_rattail_app()
return app.get_batch_handler(
'custorder',
default='rattail.batch.custorder:CustomerOrderBatchHandler')
def configure_form(self, f): def configure_form(self, f):
super().configure_form(f) super().configure_form(f)
item = f.model_instance item = f.model_instance
@ -202,8 +206,7 @@ class CustomerOrderItemView(MasterView):
f.set_renderer('order', self.render_order) f.set_renderer('order', self.render_order)
# contact # contact
batch_handler = self.get_batch_handler() if self.batch_handler.new_order_requires_customer():
if batch_handler.new_order_requires_customer():
f.remove('person') f.remove('person')
else: else:
f.remove('customer') f.remove('customer')
@ -221,7 +224,9 @@ class CustomerOrderItemView(MasterView):
elif item.pending_product and not item.product: elif item.pending_product and not item.product:
f.remove('product') f.remove('product')
# product uom # product*
if not self.creating and item.product:
f.remove('product_brand', 'product_description')
f.set_enum('product_unit_of_measure', self.enum.UNIT_OF_MEASURE) f.set_enum('product_unit_of_measure', self.enum.UNIT_OF_MEASURE)
# highlight pending fields # highlight pending fields
@ -251,8 +256,8 @@ class CustomerOrderItemView(MasterView):
# flagged # flagged
f.set_renderer('flagged', self.render_flagged) f.set_renderer('flagged', self.render_flagged)
# notes # events
f.set_renderer('notes', self.render_notes) f.set_renderer('events', self.render_events)
def render_flagged(self, item, field): def render_flagged(self, item, field):
text = "Yes" if item.flagged else "No" text = "Yes" if item.flagged else "No"
@ -375,27 +380,28 @@ class CustomerOrderItemView(MasterView):
return False return False
def render_notes(self, item, field): def render_events(self, item, field):
route_prefix = self.get_route_prefix() route_prefix = self.get_route_prefix()
factory = self.get_grid_factory() factory = self.get_grid_factory()
g = factory( g = factory(
key=f'{route_prefix}.notes', key=f'{route_prefix}.events',
data=[], data=[],
columns=[ columns=[
'created', 'occurred',
'created_by', 'type_code',
'text', 'user',
'note',
], ],
labels={ labels={
'created': "Date/Time", 'occurred': "When",
'created_by': "Added by", 'type_code': "What",
'text': "Note", 'user': "Who",
}, },
) )
table = HTML.literal( table = HTML.literal(
g.render_buefy_table_element(data_prop='notesData')) g.render_buefy_table_element(data_prop='eventsData'))
elements = [table] elements = [table]
if self.has_perm('add_note'): if self.has_perm('add_note'):
@ -412,12 +418,13 @@ class CustomerOrderItemView(MasterView):
c=elements) c=elements)
def template_kwargs_view(self, **kwargs): def template_kwargs_view(self, **kwargs):
kwargs = super(CustomerOrderItemView, self).template_kwargs_view(**kwargs) kwargs = super().template_kwargs_view(**kwargs)
model = self.model
app = self.get_rattail_app() app = self.get_rattail_app()
item = kwargs['instance'] item = kwargs['instance']
# fetch notes for current item # fetch events for current item
kwargs['notes_data'] = self.get_context_notes(item) kwargs['events_data'] = self.get_context_events(item)
# fetch "other" order items, siblings of current one # fetch "other" order items, siblings of current one
order = item.order order = item.order
@ -431,7 +438,7 @@ class CustomerOrderItemView(MasterView):
order_date = None order_date = None
if order.created: if order.created:
order_date = localtime(self.rattail_config, order.created, from_utc=True).date() order_date = app.localtime(order.created, from_utc=True).date()
other_data.append({ other_data.append({
'uuid': other.uuid, 'uuid': other.uuid,
@ -454,16 +461,18 @@ class CustomerOrderItemView(MasterView):
return kwargs return kwargs
def get_context_notes(self, item): def get_context_events(self, item):
notes = [] app = self.get_rattail_app()
for note in reversed(item.notes): events = []
created = localtime(self.rattail_config, note.created, from_utc=True) for event in item.events:
notes.append({ occurred = app.localtime(event.occurred, from_utc=True)
'created': raw_datetime(self.rattail_config, created), events.append({
'created_by': note.created_by.display_name, 'occurred': raw_datetime(self.rattail_config, occurred),
'text': note.text, 'type_code': self.enum.CUSTORDER_ITEM_EVENT.get(event.type_code, event.type_code),
'user': str(event.user),
'note': event.note,
}) })
return notes return events
def confirm_price(self): def confirm_price(self):
""" """
@ -517,6 +526,7 @@ class CustomerOrderItemView(MasterView):
""" """
View for changing status of one or more order items. View for changing status of one or more order items.
""" """
model = self.model
order_item = self.get_instance() order_item = self.get_instance()
redirect = self.redirect(self.get_action_url('view', order_item)) redirect = self.redirect(self.get_action_url('view', order_item))
@ -570,30 +580,15 @@ class CustomerOrderItemView(MasterView):
View for adding a new note to current order item, optinally View for adding a new note to current order item, optinally
also adding it to all other items under the parent order. also adding it to all other items under the parent order.
""" """
order_item = self.get_instance() item = self.get_instance()
data = self.request.json_body data = self.request.json_body
new_note = data['note']
apply_all = data['apply_all'] == True
user = self.request.user
if apply_all: self.custorder_handler.add_note(item, data['note'], self.request.user,
order_items = order_item.order.items apply_all=data['apply_all'] == True)
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.flush()
self.Session.refresh(order_item) self.Session.refresh(item)
return {'success': True, return {'events': self.get_context_events(item)}
'notes': self.get_context_notes(order_item)}
def render_order(self, item, field): def render_order(self, item, field):
order = item.order order = item.order
@ -610,22 +605,6 @@ class CustomerOrderItemView(MasterView):
url = self.request.route_url('people.view', uuid=person.uuid) url = self.request.route_url('people.view', uuid=person.uuid)
return tags.link_to(text, url) return tags.link_to(text, url)
def get_row_data(self, item):
return self.Session.query(model.CustomerOrderItemEvent)\
.filter(model.CustomerOrderItemEvent.item == item)\
.order_by(model.CustomerOrderItemEvent.occurred,
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 @classmethod
def defaults(cls, config): def defaults(cls, config):
cls._order_item_defaults(config) cls._order_item_defaults(config)