<${form.component} ref="mainForm"
+ % if master.has_perm('confirm_price'):
+ @confirm-price="showConfirmPrice"
+ % endif
% if master.has_perm('change_status'):
@change-status="showChangeStatus"
% endif
@@ -18,6 +21,45 @@
<%def name="page_content()">
${parent.page_content()}
+ % if master.has_perm('confirm_price'):
+
+ ${h.form(master.get_action_url('confirm_price', instance), ref='confirmPriceForm')}
+ ${h.csrf_token(request)}
+ ${h.hidden('note', **{':value': 'confirmPriceNote'})}
+ ${h.end_form()}
+ % endif
+
% if master.has_perm('change_status'):
@@ -190,6 +232,41 @@
${form.component_studly}Data.notesData = ${json.dumps(notes_data)|n}
+ % if master.has_perm('confirm_price'):
+
+ ThisPageData.confirmPriceShowDialog = false
+ ThisPageData.confirmPriceNote = null
+ ThisPageData.confirmPriceSubmitting = false
+
+ ThisPage.computed.confirmPriceSaveDisabled = function() {
+ if (this.confirmPriceSubmitting) {
+ return true
+ }
+ return false
+ }
+
+ ThisPage.computed.confirmPriceSubmitText = function() {
+ if (this.confirmPriceSubmitting) {
+ return "Working, please wait..."
+ }
+ return "Confirm Price"
+ }
+
+ ThisPage.methods.showConfirmPrice = function() {
+ this.confirmPriceNote = null
+ this.confirmPriceShowDialog = true
+ this.$nextTick(() => {
+ this.$refs.confirmPriceNoteField.focus()
+ })
+ }
+
+ ThisPage.methods.confirmPriceSave = function() {
+ this.confirmPriceSubmitting = true
+ this.$refs.confirmPriceForm.submit()
+ }
+
+ % endif
+
% if master.has_perm('change_status'):
ThisPageData.orderItemStatuses = ${json.dumps(enum.CUSTORDER_ITEM_STATUS)|n}
diff --git a/tailbone/views/custorders/items.py b/tailbone/views/custorders/items.py
index 2dcd43a5..737b1c20 100644
--- a/tailbone/views/custorders/items.py
+++ b/tailbone/views/custorders/items.py
@@ -98,6 +98,7 @@ class CustomerOrderItemView(MasterView):
'case_quantity',
'unit_price',
'total_price',
+ 'price_needs_confirmation',
'paid_amount',
'status_code',
'notes',
@@ -135,7 +136,7 @@ class CustomerOrderItemView(MasterView):
g.set_renderer('person', self.render_person_text)
g.set_renderer('order_created', self.render_order_created)
- g.set_enum('status_code', self.enum.CUSTORDER_ITEM_STATUS)
+ g.set_renderer('status_code', self.render_status_code_column)
g.set_label('person', "Person Name")
g.set_label('product_brand', "Brand")
@@ -160,6 +161,13 @@ class CustomerOrderItemView(MasterView):
value = localtime(self.rattail_config, item.order.created, from_utc=True)
return raw_datetime(self.rattail_config, value)
+ def render_status_code_column(self, item, field):
+ text = self.enum.CUSTORDER_ITEM_STATUS.get(item.status_code,
+ six.text_type(item.status_code))
+ if item.status_text:
+ return HTML.tag('span', title=item.status_text, c=[text])
+ return text
+
def configure_form(self, f):
super(CustomerOrderItemView, self).configure_form(f)
use_buefy = self.get_use_buefy()
@@ -178,12 +186,12 @@ class CustomerOrderItemView(MasterView):
f.set_type('cases_ordered', 'quantity')
f.set_type('units_ordered', 'quantity')
f.set_type('order_quantity', 'quantity')
-
f.set_enum('order_uom', self.enum.UNIT_OF_MEASURE)
- # currency fields
- f.set_type('unit_price', 'currency')
- f.set_type('total_price', 'currency')
+ # price fields
+ f.set_renderer('unit_price', self.render_price_with_confirmation)
+ f.set_renderer('total_price', self.render_price_with_confirmation)
+ f.set_renderer('price_needs_confirmation', self.render_price_needs_confirmation)
f.set_type('paid_amount', 'currency')
# person
@@ -198,9 +206,37 @@ class CustomerOrderItemView(MasterView):
else:
f.remove('notes')
+ def render_price_with_confirmation(self, item, field):
+ price = getattr(item, field)
+ app = self.get_rattail_app()
+ text = app.render_currency(price)
+ if item.price_needs_confirmation:
+ return HTML.tag('span', class_='has-background-warning',
+ c=[text])
+ return text
+
+ def render_price_needs_confirmation(self, item, field):
+
+ value = item.price_needs_confirmation
+ text = "Yes" if value else "No"
+ items = [text]
+
+ if value and self.has_perm('confirm_price'):
+ button = HTML.tag('b-button', type='is-primary', c="Confirm Price",
+ style='margin-left: 1rem;',
+ icon_pack='fas', icon_left='check',
+ **{'@click': "$emit('confirm-price')"})
+ items.append(button)
+
+ left = HTML.tag('div', class_='level-left', c=items)
+ outer = HTML.tag('div', class_='level', c=[left])
+ return outer
+
def render_status_code(self, item, field):
use_buefy = self.get_use_buefy()
text = self.enum.CUSTORDER_ITEM_STATUS[item.status_code]
+ if item.status_text:
+ text = "{} ({})".format(text, item.status_text)
items = [HTML.tag('span', c=[text])]
if use_buefy and self.has_perm('change_status'):
@@ -298,6 +334,32 @@ class CustomerOrderItemView(MasterView):
})
return notes
+ def confirm_price(self):
+ """
+ View for confirming price of an order item.
+ """
+ item = self.get_instance()
+ redirect = self.redirect(self.get_action_url('view', item))
+
+ # locate user responsible for change
+ user = self.request.user
+
+ # grab user-provided note to attach to event
+ note = self.request.POST.get('note')
+
+ # declare item no longer in need of price confirmation
+ item.price_needs_confirmation = False
+ item.add_event(self.enum.CUSTORDER_ITEM_EVENT_PRICE_CONFIRMED,
+ user, note=note)
+
+ # advance item to next status
+ if item.status_code == self.enum.CUSTORDER_ITEM_STATUS_INITIATED:
+ item.status_code = self.enum.CUSTORDER_ITEM_STATUS_READY
+ item.status_text = "price has been confirmed"
+
+ self.request.session.flash("Price has been confirmed.")
+ return redirect
+
def change_status(self):
"""
View for changing status of one or more order items.
@@ -342,6 +404,9 @@ class CustomerOrderItemView(MasterView):
# change status
item.status_code = new_status_code
+ # nb. must blank this out, b/c user cannot specify new
+ # text and the old text no longer applies
+ item.status_text = None
self.request.session.flash("Status has been updated to: {}".format(
self.enum.CUSTORDER_ITEM_STATUS[new_status_code]))
@@ -418,11 +483,23 @@ class CustomerOrderItemView(MasterView):
route_prefix = cls.get_route_prefix()
instance_url_prefix = cls.get_instance_url_prefix()
permission_prefix = cls.get_permission_prefix()
+ model_title = cls.get_model_title()
model_title_plural = cls.get_model_title_plural()
# fix permission group name
config.add_tailbone_permission_group(permission_prefix, model_title_plural)
+ # confirm price
+ config.add_tailbone_permission(permission_prefix,
+ '{}.confirm_price'.format(permission_prefix),
+ "Confirm price for a {}".format(model_title))
+ config.add_route('{}.confirm_price'.format(route_prefix),
+ '{}/confirm-price'.format(instance_url_prefix),
+ request_method='POST')
+ config.add_view(cls, attr='confirm_price',
+ route_name='{}.confirm_price'.format(route_prefix),
+ permission='{}.confirm_price'.format(permission_prefix))
+
# change status
config.add_tailbone_permission(permission_prefix,
'{}.change_status'.format(permission_prefix),
diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py
index 0b72f377..0153126a 100644
--- a/tailbone/views/custorders/orders.py
+++ b/tailbone/views/custorders/orders.py
@@ -36,7 +36,7 @@ from rattail.db import model
from rattail.util import pretty_quantity
from rattail.batch import get_batch_handler
-from webhelpers2.html import tags
+from webhelpers2.html import tags, HTML
from tailbone.db import Session
from tailbone.views import MasterView
@@ -189,10 +189,10 @@ class CustomerOrderView(MasterView):
g.set_type('order_quantity', 'quantity')
g.set_type('cases_ordered', 'quantity')
g.set_type('units_ordered', 'quantity')
- g.set_type('total_price', 'currency')
+ g.set_renderer('total_price', self.render_price_with_confirmation)
g.set_enum('order_uom', self.enum.UNIT_OF_MEASURE)
- g.set_enum('status_code', self.enum.CUSTORDER_ITEM_STATUS)
+ g.set_renderer('status_code', self.render_row_status_code)
g.set_label('sequence', "Seq.")
g.filters['sequence'].label = "Sequence"
@@ -206,6 +206,22 @@ class CustomerOrderView(MasterView):
g.set_link('product_brand')
g.set_link('product_description')
+ def render_price_with_confirmation(self, item, field):
+ price = getattr(item, field)
+ app = self.get_rattail_app()
+ text = app.render_currency(price)
+ if item.price_needs_confirmation:
+ return HTML.tag('span', class_='has-background-warning',
+ c=[text])
+ return text
+
+ def render_row_status_code(self, item, field):
+ text = self.enum.CUSTORDER_ITEM_STATUS.get(item.status_code,
+ six.text_type(item.status_code))
+ if item.status_text:
+ return HTML.tag('span', title=item.status_text, c=[text])
+ return text
+
def get_batch_handler(self):
return get_batch_handler(
self.rattail_config, 'custorder',
@@ -641,6 +657,7 @@ class CustomerOrderView(MasterView):
'unit_price_display': "${:0.2f}".format(row.unit_price) if row.unit_price is not None else None,
'total_price': six.text_type(row.total_price) if row.total_price is not None else None,
'total_price_display': "${:0.2f}".format(row.total_price) if row.total_price is not None else None,
+ 'price_needs_confirmation': row.price_needs_confirmation,
'status_code': row.status_code,
'status_text': row.status_text,
@@ -684,7 +701,8 @@ class CustomerOrderView(MasterView):
row = self.handler.add_product(batch, product,
decimal.Decimal(data.get('order_quantity') or '0'),
- data.get('order_uom'))
+ data.get('order_uom'),
+ price_needs_confirmation=data.get('price_needs_confirmation'))
self.Session.flush()
else: # product is not known
@@ -719,6 +737,7 @@ class CustomerOrderView(MasterView):
row.product = product
row.order_quantity = decimal.Decimal(data.get('order_quantity') or '0')
row.order_uom = data.get('order_uom')
+ row.price_needs_confirmation = data.get('price_needs_confirmation')
self.handler.refresh_row(row)
self.Session.flush()
self.Session.refresh(row)