Add basic "price needs confirmation" support for custorder
This commit is contained in:
parent
93b752f436
commit
8b044dbb22
|
@ -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">
|
||||
<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) {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue