Add basic "price needs confirmation" support for custorder

This commit is contained in:
Lance Edgar 2021-10-18 18:28:28 -05:00
parent 93b752f436
commit 8b044dbb22
4 changed files with 214 additions and 11 deletions

View file

@ -500,7 +500,8 @@
</b-radio>
</div>
<div v-show="productIsKnown">
<div v-show="productIsKnown"
style="padding-left: 5rem;">
<b-field grouped>
<b-field label="Description" horizontal expanded>
@ -544,8 +545,28 @@
</b-button>
</b-field>
<div v-if="productUUID">
<b-field grouped
v-if="productUUID">
<b-field label="Unit Price">
<span :class="productPriceNeedsConfirmation ? 'has-background-warning' : ''">
$4.20 / EA
</span>
</b-field>
<b-field label="Last Changed">
<span>2021-01-01</span>
</b-field>
</b-field>
<b-checkbox v-model="productPriceNeedsConfirmation"
size="is-small">
This price is questionable and should be confirmed
by someone before order proceeds.
</b-checkbox>
</div>
</div>
<br />
<div class="field">
<b-radio v-model="productIsKnown" disabled
:native-value="false">
@ -619,7 +640,9 @@
</b-table-column>
<b-table-column field="total_price_display" label="Total">
{{ props.row.total_price_display }}
<span :class="props.row.price_needs_confirmation ? 'has-background-warning' : ''">
{{ props.row.total_price_display }}
</span>
</b-table-column>
<b-table-column field="vendor_display" label="Vendor">
@ -742,6 +765,7 @@
defaultUOM: defaultUOM,
productUOM: defaultUOM,
productCaseSize: null,
productPriceNeedsConfirmation: false,
## TODO: should find a better way to handle CSRF token
csrftoken: ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n},
@ -1271,6 +1295,7 @@
this.productQuantity = 1
this.productUnitChoices = this.defaultUnitChoices
this.productUOM = this.defaultUOM
this.productPriceNeedsConfirmation = false
this.showingItemDialog = true
this.$nextTick(() => {
this.$refs.productDescriptionAutocomplete.focus()
@ -1287,6 +1312,7 @@
this.productQuantity = row.order_quantity
this.productUnitChoices = row.order_uom_choices
this.productUOM = row.order_uom
this.productPriceNeedsConfirmation = row.price_needs_confirmation
this.showingItemDialog = true
},
@ -1319,6 +1345,7 @@
this.productDisplay = null
this.productUPC = null
this.productUnitChoices = this.defaultUnitChoices
this.productPriceNeedsConfirmation = false
if (autofocus) {
this.$nextTick(() => {
this.$refs.productUPCInput.focus()
@ -1358,6 +1385,7 @@
this.productUPC = response.data.upc_pretty
this.productDisplay = response.data.full_description
this.setProductUnitChoices(response.data.uom_choices)
this.productPriceNeedsConfirmation = false
}
})
},
@ -1379,6 +1407,7 @@
this.productUPC = response.data.upc_pretty
this.productDisplay = response.data.full_description
this.setProductUnitChoices(response.data.uom_choices)
this.productPriceNeedsConfirmation = false
})
} else {
this.clearProduct()
@ -1392,6 +1421,7 @@
product_uuid: this.productUUID,
order_quantity: this.productQuantity,
order_uom: this.productUOM,
price_needs_confirmation: this.productPriceNeedsConfirmation,
}
if (this.editingItem) {

View file

@ -4,6 +4,9 @@
<%def name="render_buefy_form()">
<div class="form">
<${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'):
<b-modal has-modal-card
:active.sync="confirmPriceShowDialog">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Confirm Price</p>
</header>
<section class="modal-card-body">
<p>
Please provide a note</span>:
</p>
<b-input v-model="confirmPriceNote"
ref="confirmPriceNoteField"
type="textarea" rows="2">
</b-input>
</section>
<footer class="modal-card-foot">
<b-button type="is-primary"
@click="confirmPriceSave()"
:disabled="confirmPriceSaveDisabled"
icon-pack="fas"
icon-left="check">
{{ confirmPriceSubmitText }}
</b-button>
<b-button @click="confirmPriceShowDialog = false">
Cancel
</b-button>
</footer>
</div>
</b-modal>
${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'):
<b-modal :active.sync="showChangeStatusDialog">
<div class="card">
@ -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}

View file

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

View file

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