Refactor inventory batch "add row" page, per new theme

This commit is contained in:
Lance Edgar 2023-01-11 19:24:50 -06:00
parent 2c7f2c0fcd
commit 4746b6fae9
4 changed files with 369 additions and 92 deletions

View file

@ -1,10 +1,11 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/base.mako" /> <%inherit file="/form.mako" />
<%def name="title()">Inventory Form</%def> <%def name="title()">Inventory Form</%def>
<%def name="extra_javascript()"> <%def name="extra_javascript()">
${parent.extra_javascript()} ${parent.extra_javascript()}
% if not use_buefy:
${h.javascript_link(request.static_url('tailbone:static/js/numeric.js'))} ${h.javascript_link(request.static_url('tailbone:static/js/numeric.js'))}
<script type="text/javascript"> <script type="text/javascript">
@ -200,10 +201,12 @@
}); });
</script> </script>
% endif
</%def> </%def>
<%def name="extra_styles()"> <%def name="extra_styles()">
${parent.extra_styles()} ${parent.extra_styles()}
% if not use_buefy:
<style type="text/css"> <style type="text/css">
#product-info { #product-info {
@ -226,76 +229,337 @@
} }
</style> </style>
% endif
</%def> </%def>
<%def name="context_menu_items()"> <%def name="context_menu_items()">
${parent.context_menu_items()}
<li>${h.link_to("Back to Inventory Batch", url('batch.inventory.view', uuid=batch.uuid))}</li> <li>${h.link_to("Back to Inventory Batch", url('batch.inventory.view', uuid=batch.uuid))}</li>
</%def> </%def>
<%def name="render_form()">
% if use_buefy:
<ul id="context-menu"> <script type="text/x-template" id="${form.component}-template">
${self.context_menu_items()} <div class="product-info">
</ul>
<div class="form-wrapper"> ${h.form(form.action_url, **{'@submit': 'handleSubmit'})}
${h.form(form.action_url, id='inventory-form')} ${h.csrf_token(request)}
${h.csrf_token(request)}
<div class="field-wrapper"> ${h.hidden('product', **{':value': 'productInfo.uuid'})}
<label for="upc">Product UPC</label> ${h.hidden('upc', **{':value': 'productInfo.upc'})}
<div class="field"> ${h.hidden('brand_name', **{':value': 'productInfo.brand_name'})}
${h.hidden('product')} ${h.hidden('description', **{':value': 'productInfo.description'})}
<div>${h.text('upc', autocomplete='off')}</div> ${h.hidden('size', **{':value': 'productInfo.size'})}
<div id="product-info"> ${h.hidden('case_quantity', **{':value': 'productInfo.case_quantity'})}
<p>please ENTER a scancode</p>
<div class="img-wrapper"><img /></div> <b-field label="Product UPC" horizontal>
<div class="warning notfound">please confirm UPC and provide more details</div> <div style="display: flex; flex-direction: column;">
<div class="warning present">product already exists in batch, please confirm count</div> <b-input v-model="productUPC"
<div class="warning force-unit">pack item scanned, but must count units instead</div> ref="productUPC"
@input="productChanged"
@keydown.native="productKeydown">
</b-input>
<div class="has-text-centered block">
<p v-if="!productInfo.uuid"
class="block">
please ENTER a scancode
</p>
<p v-if="productInfo.uuid"
class="block">
{{ productInfo.full_description }}
</p>
<div style="min-height: 150px; margin: 0.5rem 0;">
<img v-if="productInfo.uuid"
:src="productInfo.image_url" />
</div>
<div v-if="alreadyPresentInBatch"
class="has-background-danger">
product already exists in batch, please confirm count
</div>
<div v-if="forceUnitItem"
class="has-background-danger">
pack item scanned, but must count units instead
</div>
## <div v-if="productNotFound"
## class="has-background-danger">
## please confirm UPC and provide more details
## </div>
</div>
</div>
</b-field>
## <div v-if="productNotFound"
## ## class="product-fields"
## >
##
## <div class="field-wrapper brand_name">
## <label for="brand_name">Brand Name</label>
## <div class="field">${h.text('brand_name')}</div>
## </div>
##
## <div class="field-wrapper description">
## <label for="description">Description</label>
## <div class="field">${h.text('description')}</div>
## </div>
##
## <div class="field-wrapper size">
## <label for="size">Size</label>
## <div class="field">${h.text('size')}</div>
## </div>
##
## <div class="field-wrapper case_quantity">
## <label for="case_quantity">Units in Case</label>
## <div class="field">${h.text('case_quantity')}</div>
## </div>
##
## </div>
% if allow_cases:
<b-field label="Cases" horizontal>
<b-input name="cases"
v-model="productCases"
ref="productCases"
:disabled="!productInfo.uuid">
</b-input>
</b-field>
% endif
<b-field label="Units" horizontal>
<b-input name="units"
v-model="productUnits"
ref="productUnits"
:disabled="!productInfo.uuid">
</b-input>
</b-field>
<b-button type="is-primary"
native-type="submit"
:disabled="submitting">
{{ submitting ? "Working, please wait..." : "Submit" }}
</b-button>
${h.end_form()}
</div>
</script>
<script type="text/javascript">
let ${form.component_studly} = {
template: '#${form.component}-template',
mounted() {
this.$refs.productUPC.focus()
},
methods: {
clearProduct() {
this.productInfo = {}
## this.productNotFound = false
this.alreadyPresentInBatch = false
this.forceUnitItem = false
this.productCases = null
this.productUnits = null
},
assertQuantity() {
% if allow_cases:
let cases = parseFloat(this.productCases)
if (!isNaN(cases)) {
if (cases > 999999) {
alert("Case amount is invalid!")
this.$refs.productCases.focus()
return false
}
return true
}
% endif
let units = parseFloat(this.productUnits)
if (!isNaN(units)) {
if (units > 999999) {
alert("Unit amount is invalid!")
this.$refs.productUnits.focus()
return false
}
return true
}
alert("Please provide case and/or unit quantity")
% if allow_cases:
this.$refs.productCases.focus()
% else:
this.$refs.productUnits.focus()
% endif
},
handleSubmit(event) {
if (!this.assertQuantity()) {
event.preventDefault()
return
}
this.submitting = true
},
productChanged() {
this.clearProduct()
},
productKeydown(event) {
if (event.which == 13) { // ENTER
this.productLookup()
event.preventDefault()
}
},
productLookup() {
let url = '${url('batch.inventory.desktop_lookup', uuid=batch.uuid)}'
let params = {
upc: this.productUPC,
}
this.$http.get(url, {params: params}).then(response => {
if (response.data.error) {
alert(response.data.error)
if (response.data.redirect) {
location.href = response.data.redirect
}
} else if (response.data.product.uuid) {
this.productUPC = response.data.product.upc_pretty
this.productInfo = response.data.product
this.forceUnitItem = response.data.force_unit_item
this.alreadyPresentInBatch = response.data.already_present_in_batch
if (this.alreadyPresentInBatch) {
this.productCases = response.data.cases
this.productUnits = response.data.units
} else if (this.productInfo.type2) {
this.productUnits = this.productInfo.units
}
this.$nextTick(() => {
if (this.productInfo.type2) {
this.$refs.productUnits.focus()
} else {
% if allow_cases and prefer_cases:
if (this.productCases) {
this.$refs.productCases.focus()
} else if (this.productUnits) {
this.$refs.productUnits.focus()
} else {
this.$refs.productCases.focus()
}
% else:
this.$refs.productUnits.focus()
% endif
}
})
} else {
## this.productNotFound = true
alert("Product not found!")
}
})
},
},
}
let ${form.component_studly}Data = {
submitting: false,
productUPC: null,
## productNotFound: false,
productInfo: {},
% if allow_cases:
productCases: null,
% endif
productUnits: null,
alreadyPresentInBatch: false,
forceUnitItem: false,
}
</script>
% else:
## not buefy
<div class="form-wrapper">
${h.form(form.action_url, id='inventory-form')}
${h.csrf_token(request)}
<div class="field-wrapper">
<label for="upc">Product UPC</label>
<div class="field">
${h.hidden('product')}
<div>${h.text('upc', autocomplete='off')}</div>
<div id="product-info">
<p>please ENTER a scancode</p>
<div class="img-wrapper"><img /></div>
<div class="warning notfound">please confirm UPC and provide more details</div>
<div class="warning present">product already exists in batch, please confirm count</div>
<div class="warning force-unit">pack item scanned, but must count units instead</div>
</div>
</div>
</div>
<div class="product-fields" style="display: none;">
<div class="field-wrapper brand_name">
<label for="brand_name">Brand Name</label>
<div class="field">${h.text('brand_name')}</div>
</div>
<div class="field-wrapper description">
<label for="description">Description</label>
<div class="field">${h.text('description')}</div>
</div>
<div class="field-wrapper size">
<label for="size">Size</label>
<div class="field">${h.text('size')}</div>
</div>
<div class="field-wrapper case_quantity">
<label for="case_quantity">Units in Case</label>
<div class="field">${h.text('case_quantity')}</div>
</div>
</div>
% if allow_cases:
<div class="field-wrapper cases">
<label for="cases">Cases</label>
<div class="field">${h.text('cases', autocomplete='off')}</div>
</div>
% endif
<div class="field-wrapper units">
<label for="units">Units</label>
<div class="field">${h.text('units', autocomplete='off')}</div>
</div>
<div class="buttons">
${h.submit('submit', "Submit")}
</div>
${h.end_form()}
</div> </div>
</div>
</div>
<div class="product-fields" style="display: none;">
<div class="field-wrapper brand_name">
<label for="brand_name">Brand Name</label>
<div class="field">${h.text('brand_name')}</div>
</div>
<div class="field-wrapper description">
<label for="description">Description</label>
<div class="field">${h.text('description')}</div>
</div>
<div class="field-wrapper size">
<label for="size">Size</label>
<div class="field">${h.text('size')}</div>
</div>
<div class="field-wrapper case_quantity">
<label for="case_quantity">Units in Case</label>
<div class="field">${h.text('case_quantity')}</div>
</div>
</div>
% if allow_cases:
<div class="field-wrapper cases">
<label for="cases">Cases</label>
<div class="field">${h.text('cases', autocomplete='off')}</div>
</div>
% endif % endif
</%def>
<div class="field-wrapper units">
<label for="units">Units</label>
<div class="field">${h.text('units', autocomplete='off')}</div>
</div>
<div class="buttons"> ${parent.body()}
${h.submit('submit', "Submit")}
</div>
${h.end_form()}
</div>

View file

@ -57,7 +57,7 @@
<%def name="before_object_helpers()"></%def> <%def name="before_object_helpers()"></%def>
<%def name="render_this_page_template()"> <%def name="render_this_page_template()">
% if form is not Underined: % if form is not Undefined:
${self.render_form()} ${self.render_form()}
% endif % endif
${parent.render_this_page_template()} ${parent.render_this_page_template()}

View file

@ -705,9 +705,15 @@ class BatchMasterView(MasterView):
if self.rows_creatable and not batch.executed and not batch.complete: if self.rows_creatable and not batch.executed and not batch.complete:
permission_prefix = self.get_permission_prefix() permission_prefix = self.get_permission_prefix()
if self.request.has_perm('{}.create_row'.format(permission_prefix)): if self.request.has_perm('{}.create_row'.format(permission_prefix)):
link = tags.link_to("Create a new {}".format(self.get_row_model_title()), url = self.get_action_url('create_row', batch)
self.get_action_url('create_row', batch)) if self.get_use_buefy():
return HTML.tag('p', c=[link]) return self.make_buefy_button("New Row", url=url,
is_primary=True,
icon_left='plus')
else:
text = "Create a new {}".format(self.get_row_model_title())
link = tags.link_to(text, url)
return HTML.tag('p', c=[link])
def make_batch_row_grid_tools(self, batch): def make_batch_row_grid_tools(self, batch):
if self.get_use_buefy(): if self.get_use_buefy():

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2021 Lance Edgar # Copyright © 2010-2023 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -234,40 +234,47 @@ class InventoryBatchView(BatchMasterView):
if batch.executed: if batch.executed:
return self.redirect(self.get_action_url('view', batch)) return self.redirect(self.get_action_url('view', batch))
use_buefy = self.get_use_buefy()
schema = DesktopForm().bind(session=self.Session()) schema = DesktopForm().bind(session=self.Session())
form = forms.Form(schema=schema, request=self.request) form = forms.Form(schema=schema, request=self.request, use_buefy=use_buefy)
if form.validate(newstyle=True): if self.request.method == 'POST':
if form.validate(newstyle=True):
product = self.Session.query(model.Product).get(form.validated['product']) product = self.Session.query(model.Product).get(form.validated['product'])
row = None row = None
if self.should_aggregate_products(batch): if self.should_aggregate_products(batch):
row = self.find_row_for_product(batch, product) row = self.find_row_for_product(batch, product)
if row: if row:
row.cases = form.validated['cases']
row.units = form.validated['units']
self.handler.refresh_row(row)
if not row:
row = model.InventoryBatchRow()
row.product = product
row.upc = form.validated['upc']
row.brand_name = form.validated['brand_name']
row.description = form.validated['description']
row.size = form.validated['size']
row.case_quantity = form.validated['case_quantity']
row.cases = form.validated['cases'] row.cases = form.validated['cases']
row.units = form.validated['units'] row.units = form.validated['units']
self.handler.refresh_row(row) self.handler.capture_current_units(row)
self.handler.add_row(batch, row)
if not row: description = make_full_description(form.validated['brand_name'],
row = model.InventoryBatchRow() form.validated['description'],
row.product = product form.validated['size'])
row.upc = form.validated['upc'] self.request.session.flash("{} cases, {} units: {} {}".format(
row.brand_name = form.validated['brand_name'] form.validated['cases'] or 0, form.validated['units'] or 0,
row.description = form.validated['description'] form.validated['upc'].pretty(), description))
row.size = form.validated['size'] return self.redirect(self.request.current_route_url())
row.case_quantity = form.validated['case_quantity']
row.cases = form.validated['cases']
row.units = form.validated['units']
self.handler.capture_current_units(row)
self.handler.add_row(batch, row)
description = make_full_description(form.validated['brand_name'], else:
form.validated['description'], dform = form.make_deform_form()
form.validated['size']) msg = "Form did not validate: {}".format(six.text_type(dform.error))
self.request.session.flash("{} cases, {} units: {} {}".format( self.request.session.flash(msg, 'error')
form.validated['cases'] or 0, form.validated['units'] or 0,
form.validated['upc'].pretty(), description))
return self.redirect(self.request.current_route_url())
title = self.get_instance_title(batch) title = self.get_instance_title(batch)
return self.render_to_response('desktop_form', { return self.render_to_response('desktop_form', {