Add way to prevent "case" entries for inventory adjustment batch

This commit is contained in:
Lance Edgar 2018-05-23 14:48:17 -05:00
parent 57c2a7981f
commit 54bfafdbfe
4 changed files with 80 additions and 30 deletions

View file

@ -9,14 +9,20 @@
<script type="text/javascript"> <script type="text/javascript">
function assert_quantity() { function assert_quantity() {
% if allow_cases:
if ($('#cases').val() && parseFloat($('#cases').val())) { if ($('#cases').val() && parseFloat($('#cases').val())) {
return true; return true;
} }
% endif
if ($('#units').val() && parseFloat($('#units').val())) { if ($('#units').val() && parseFloat($('#units').val())) {
return true; return true;
} }
alert("Please provide case and/or unit quantity"); alert("Please provide case and/or unit quantity");
% if allow_cases:
$('#cases').select().focus(); $('#cases').select().focus();
% else:
$('#units').select().focus();
% endif
return false; return false;
} }
@ -24,7 +30,9 @@
$('#product-info p').text(msg); $('#product-info p').text(msg);
$('#product-info img').hide(); $('#product-info img').hide();
$('#upc').focus().select(); $('#upc').focus().select();
% if allow_cases:
$('.field-wrapper.cases input').prop('disabled', true); $('.field-wrapper.cases input').prop('disabled', true);
% endif
$('.field-wrapper.units input').prop('disabled', true); $('.field-wrapper.units input').prop('disabled', true);
$('.buttons button').button('disable'); $('.buttons button').button('disable');
} }
@ -65,7 +73,9 @@
$('#product-info .warning').hide(); $('#product-info .warning').hide();
$('.product-fields').hide(); $('.product-fields').hide();
// $('.receiving-fields').hide(); // $('.receiving-fields').hide();
% if allow_cases:
$('.field-wrapper.cases input').prop('disabled', true); $('.field-wrapper.cases input').prop('disabled', true);
% endif
$('.field-wrapper.units input').prop('disabled', true); $('.field-wrapper.units input').prop('disabled', true);
$('.buttons button').button('disable'); $('.buttons button').button('disable');
return true; return true;
@ -102,13 +112,19 @@
$('.product-fields').show(); $('.product-fields').show();
} }
$('#product-info .warning.notordered').show(); $('#product-info .warning.notordered').show();
% if allow_cases:
$('.field-wrapper.cases input').prop('disabled', false); $('.field-wrapper.cases input').prop('disabled', false);
% endif
$('.field-wrapper.units input').prop('disabled', false); $('.field-wrapper.units input').prop('disabled', false);
$('.buttons button').button('enable'); $('.buttons button').button('enable');
if (data.product.type2) { if (data.product.type2) {
$('#units').focus().select(); $('#units').focus().select();
} else { } else {
% if allow_cases:
$('#cases').focus().select(); $('#cases').focus().select();
% else:
$('#units').focus().select();
% endif
} }
// TODO: this is maybe useful if "new products" may be added via inventory batch // TODO: this is maybe useful if "new products" may be added via inventory batch
@ -147,7 +163,9 @@
}); });
$('#upc').focus(); $('#upc').focus();
% if allow_cases:
$('.field-wrapper.cases input').prop('disabled', true); $('.field-wrapper.cases input').prop('disabled', true);
% endif
$('.field-wrapper.units input').prop('disabled', true); $('.field-wrapper.units input').prop('disabled', true);
$('.buttons button').button('disable'); $('.buttons button').button('disable');
@ -232,10 +250,12 @@
</div> </div>
<div class="field-wrapper cases"> % if allow_cases:
<label for="cases">Cases</label> <div class="field-wrapper cases">
<div class="field">${h.text('cases', autocomplete='off')}</div> <label for="cases">Cases</label>
</div> <div class="field">${h.text('cases', autocomplete='off')}</div>
</div>
% endif
<div class="field-wrapper units"> <div class="field-wrapper units">
<label for="units">Units</label> <label for="units">Units</label>

View file

@ -3,22 +3,19 @@
<%namespace file="/mobile/keypad.mako" import="keypad" /> <%namespace file="/mobile/keypad.mako" import="keypad" />
## TODO: this is broken for actual page (header) title ## TODO: this is broken for actual page (header) title
<%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} &raquo; ${h.link_to(instance.batch.id_str, url('mobile.batch.inventory.view', uuid=instance.batch_uuid))} &raquo; ${row.upc.pretty()}</%def> <%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} &raquo; ${h.link_to(batch.id_str, url('mobile.batch.inventory.view', uuid=batch.uuid))} &raquo; ${row.upc.pretty()}</%def>
<% <%
unit_uom = 'LB' if row.product and row.product.weighed else 'EA' unit_uom = 'LB' if row.product and row.product.weighed else 'EA'
if row.cases: if row.cases and allow_cases:
uom = 'CS' uom = 'CS'
elif row.units: elif row.units:
if row.product and row.product.weighed: uom = unit_uom
uom = 'LB' elif row.case_quantity and allow_cases:
else:
uom = 'EA'
elif row.case_quantity:
uom = 'CS' uom = 'CS'
else: else:
uom = 'EA' uom = unit_uom
%> %>
<div class="ui-grid-a"> <div class="ui-grid-a">
@ -46,20 +43,22 @@
${uom} ${uom}
</p> </p>
% if not row.batch.executed and not row.batch.complete: % if not batch.executed and not batch.complete:
${h.form(request.current_route_url())} ${h.form(request.current_route_url())}
${h.csrf_token(request)} ${h.csrf_token(request)}
${h.hidden('row', value=row.uuid)} ${h.hidden('row', value=row.uuid)}
% if allow_cases:
${h.hidden('cases')} ${h.hidden('cases')}
% endif
${h.hidden('units')} ${h.hidden('units')}
${keypad(unit_uom, uom, quantity=row.cases or row.units or 1)} ${keypad(unit_uom, uom, quantity=(row.cases or row.units or 1) if allow_cases else (row.units or 1), allow_cases=allow_cases)}
<fieldset data-role="controlgroup" data-type="horizontal" class="inventory-actions"> <fieldset data-role="controlgroup" data-type="horizontal" class="inventory-actions">
<button type="button" class="ui-btn-inline ui-corner-all save">Save</button> <button type="button" class="ui-btn-inline ui-corner-all save">Save</button>
<button type="button" class="ui-btn-inline ui-corner-all delete" disabled="disabled">Delete</button> <button type="button" class="ui-btn-inline ui-corner-all delete" disabled="disabled">Delete</button>
${h.link_to("Cancel", url('mobile.batch.inventory.view', uuid=row.batch.uuid), class_='ui-btn ui-btn-inline ui-corner-all')} ${h.link_to("Cancel", url('mobile.batch.inventory.view', uuid=batch.uuid), class_='ui-btn ui-btn-inline ui-corner-all')}
</fieldset> </fieldset>
${h.end_form()} ${h.end_form()}

View file

@ -1,6 +1,6 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%def name="keypad(unit_uom, selected_uom, quantity=1)"> <%def name="keypad(unit_uom, selected_uom, quantity=1, allow_cases=True)">
<div class="quantity-keypad-thingy" data-changed="false"> <div class="quantity-keypad-thingy" data-changed="false">
<table> <table>
@ -31,7 +31,9 @@
<fieldset data-role="controlgroup" data-type="horizontal"> <fieldset data-role="controlgroup" data-type="horizontal">
<button type="button" class="ui-btn-active keypad-quantity">${h.pretty_quantity(quantity or 1)}</button> <button type="button" class="ui-btn-active keypad-quantity">${h.pretty_quantity(quantity or 1)}</button>
<button type="button" disabled="disabled">&nbsp;</button> <button type="button" disabled="disabled">&nbsp;</button>
% if allow_cases:
${h.radio('keypad-uom', value='CS', checked=selected_uom == 'CS', label="CS")} ${h.radio('keypad-uom', value='CS', checked=selected_uom == 'CS', label="CS")}
% endif
${h.radio('keypad-uom', value=unit_uom, checked=selected_uom == unit_uom, label=unit_uom)} ${h.radio('keypad-uom', value=unit_uom, checked=selected_uom == unit_uom, label=unit_uom)}
</fieldset> </fieldset>

View file

@ -97,6 +97,10 @@ class InventoryBatchView(BatchMasterView):
# set to False to disable "zero all" batch count mode # set to False to disable "zero all" batch count mode
allow_zero_all = True allow_zero_all = True
# set to False to prevent exposing case fields for user input,
# when the batch count mode is "adjust only"
allow_adjustment_cases = True
labels = { labels = {
'mode': "Count Mode", 'mode': "Count Mode",
} }
@ -310,8 +314,16 @@ class InventoryBatchView(BatchMasterView):
'index_url': self.get_action_url('view', batch), 'index_url': self.get_action_url('view', batch),
'form': form, 'form': form,
'dform': form.make_deform_form(), 'dform': form.make_deform_form(),
'allow_cases': self.allow_cases(batch),
}) })
def allow_cases(self, batch):
if batch.mode == self.enum.INVENTORY_MODE_ADJUST:
if self.allow_adjustment_cases:
return True
return False
return True
def desktop_lookup(self): def desktop_lookup(self):
""" """
Try to locate a product by UPC, and validate it in the context of Try to locate a product by UPC, and validate it in the context of
@ -463,23 +475,26 @@ class InventoryBatchView(BatchMasterView):
""" """
self.viewing = True self.viewing = True
row = self.get_row_instance() row = self.get_row_instance()
parent = self.get_parent(row) batch = self.get_parent(row)
form = self.make_mobile_row_form(row) form = self.make_mobile_row_form(row)
context = { context = {
'row': row, 'row': row,
'batch': batch,
'instance': row, 'instance': row,
'instance_title': self.get_row_instance_title(row), 'instance_title': self.get_row_instance_title(row),
'parent_model_title': self.get_model_title(), 'parent_model_title': self.get_model_title(),
'parent_title': self.get_instance_title(parent), 'parent_title': self.get_instance_title(batch),
'parent_url': self.get_action_url('view', parent, mobile=True), 'parent_url': self.get_action_url('view', batch, mobile=True),
'product_image_url': pod.get_image_url(self.rattail_config, row.upc), 'product_image_url': pod.get_image_url(self.rattail_config, row.upc),
'form': form, 'form': form,
'allow_cases': self.allow_cases(batch),
} }
if self.request.has_perm('{}.edit_row'.format(self.get_permission_prefix())): if self.request.has_perm('{}.edit_row'.format(self.get_permission_prefix())):
update_form = forms.Form(schema=InventoryForm(), request=self.request) schema = InventoryForm().bind(session=self.Session())
update_form = forms.Form(schema=schema, request=self.request)
if update_form.validate(newstyle=True): if update_form.validate(newstyle=True):
row = update_form.validated['row'] row = self.Session.query(model.InventoryBatchRow).get(update_form.validated['row'])
cases = update_form.validated['cases'] cases = update_form.validated['cases']
units = update_form.validated['units'] units = update_form.validated['units']
if cases: if cases:
@ -489,7 +504,8 @@ class InventoryBatchView(BatchMasterView):
row.cases = None row.cases = None
row.units = units row.units = units
self.handler.refresh_row(row) self.handler.refresh_row(row)
return self.redirect(self.request.route_url('mobile.{}.view'.format(self.get_route_prefix()), uuid=row.batch_uuid)) route_prefix = self.get_route_prefix()
return self.redirect(self.request.route_url('mobile.{}.view'.format(route_prefix), uuid=batch.uuid))
return self.render_to_response('view_row', context, mobile=True) return self.render_to_response('view_row', context, mobile=True)
@ -533,6 +549,7 @@ class InventoryBatchView(BatchMasterView):
def configure_row_form(self, f): def configure_row_form(self, f):
super(InventoryBatchView, self).configure_row_form(f) super(InventoryBatchView, self).configure_row_form(f)
row = f.model_instance
# readonly fields # readonly fields
f.set_readonly('upc') f.set_readonly('upc')
@ -557,6 +574,11 @@ class InventoryBatchView(BatchMasterView):
# upc # upc
f.set_renderer('upc', self.render_upc) f.set_renderer('upc', self.render_upc)
# cases
if self.editing:
if not self.allow_cases(row.batch):
f.set_readonly('cases')
def render_upc(self, row, field): def render_upc(self, row, field):
upc = row.upc upc = row.upc
if not upc: if not upc:
@ -599,19 +621,26 @@ class InventoryBatchView(BatchMasterView):
permission='{}.create_row'.format(permission_prefix)) permission='{}.create_row'.format(permission_prefix))
class InventoryBatchRowType(forms.types.ObjectType): # TODO: this is a stopgap measure to fix an obvious bug, which exists when the
model_class = model.InventoryBatchRow # session is not provided by the view at runtime (i.e. when it was instead
# being provided by the type instance, which was created upon app startup).
def deserialize(self, node, cstruct): @colander.deferred
row = super(InventoryBatchRowType, self).deserialize(node, cstruct) def valid_inventory_batch_row(node, kw):
if row and row.batch.executed: session = kw['session']
def validate(node, value):
row = session.query(model.InventoryBatchRow).get(value)
if not row:
raise colander.Invalid(node, "Batch row not found")
if row.batch.executed:
raise colander.Invalid(node, "Batch has already been executed") raise colander.Invalid(node, "Batch has already been executed")
return row return row.uuid
return validate
class InventoryForm(colander.MappingSchema): class InventoryForm(colander.MappingSchema):
row = colander.SchemaNode(InventoryBatchRowType()) row = colander.SchemaNode(colander.String(),
validator=valid_inventory_batch_row)
cases = colander.SchemaNode(colander.Decimal(), missing=colander.null) cases = colander.SchemaNode(colander.Decimal(), missing=colander.null)