Add new "receive row" view for mobile receiving
this frees us up to dumb-down the "view row" which thus far has been tasked with actual receiving
This commit is contained in:
parent
b1c77afc81
commit
05481f7828
151
tailbone/templates/mobile/receiving/receive_row.mako
Normal file
151
tailbone/templates/mobile/receiving/receive_row.mako
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/mobile/master/view_row.mako" />
|
||||||
|
<%namespace file="/mobile/keypad.mako" import="keypad" />
|
||||||
|
|
||||||
|
<%def name="title()">Receiving » ${batch.id_str} » ${master.render_product_key_value(row)}</%def>
|
||||||
|
|
||||||
|
<%def name="page_title()">${h.link_to("Receiving", url('mobile.receiving'))} » ${h.link_to(batch.id_str, url('mobile.receiving.view', uuid=batch.uuid))} » ${master.render_product_key_value(row)}</%def>
|
||||||
|
|
||||||
|
|
||||||
|
<div${' class="ui-grid-a"' if product_image_url else ''|n}>
|
||||||
|
<div class="ui-block-a"${'' if instance.product else ' style="background-color: red;"'|n}>
|
||||||
|
% if instance.product:
|
||||||
|
<h3>${instance.brand_name or ""}</h3>
|
||||||
|
<h3>${instance.description} ${instance.size or ''}</h3>
|
||||||
|
% if allow_cases:
|
||||||
|
<h3>1 CS = ${h.pretty_quantity(row.case_quantity or 1)} ${unit_uom}</h3>
|
||||||
|
% endif
|
||||||
|
% else:
|
||||||
|
<h3>${instance.description}</h3>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
|
% if product_image_url:
|
||||||
|
<div class="ui-block-b">
|
||||||
|
${h.image(product_image_url, "product image")}
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table${'' if instance.product else ' style="background-color: red;"'|n}>
|
||||||
|
<tbody>
|
||||||
|
% if batch.order_quantities_known:
|
||||||
|
<tr>
|
||||||
|
<td>ordered</td>
|
||||||
|
<td>
|
||||||
|
% if allow_cases:
|
||||||
|
${h.pretty_quantity(row.cases_ordered or 0)} /
|
||||||
|
% endif
|
||||||
|
${h.pretty_quantity(row.units_ordered or 0)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
% endif
|
||||||
|
<tr>
|
||||||
|
<td>received</td>
|
||||||
|
<td>
|
||||||
|
% if allow_cases:
|
||||||
|
${h.pretty_quantity(row.cases_received or 0)} /
|
||||||
|
% endif
|
||||||
|
${h.pretty_quantity(row.units_received or 0)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>damaged</td>
|
||||||
|
<td>
|
||||||
|
% if allow_cases:
|
||||||
|
${h.pretty_quantity(row.cases_damaged or 0)} /
|
||||||
|
% endif
|
||||||
|
${h.pretty_quantity(row.units_damaged or 0)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
% if allow_expired:
|
||||||
|
<tr>
|
||||||
|
<td>expired</td>
|
||||||
|
<td>
|
||||||
|
% if allow_cases:
|
||||||
|
${h.pretty_quantity(row.cases_expired or 0)} /
|
||||||
|
% endif
|
||||||
|
${h.pretty_quantity(row.units_expired or 0)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
% endif
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
% if request.session.peek_flash('receiving-warning'):
|
||||||
|
% for error in request.session.pop_flash('receiving-warning'):
|
||||||
|
<div class="receiving-warning">${error}</div>
|
||||||
|
% endfor
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if not batch.executed and not batch.complete:
|
||||||
|
|
||||||
|
${h.form(request.current_route_url(), class_='receiving-update')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.hidden('row', value=row.uuid)}
|
||||||
|
${h.hidden('cases')}
|
||||||
|
${h.hidden('units')}
|
||||||
|
|
||||||
|
## only show quick-receive if we have an identifiable product
|
||||||
|
% if quick_receive and instance.product:
|
||||||
|
% if quick_receive_all:
|
||||||
|
<button type="button" class="quick-receive" data-quantity="${quick_receive_quantity}" data-uom="${quick_receive_uom}">${quick_receive_text}</button>
|
||||||
|
% elif allow_cases:
|
||||||
|
<button type="button" class="quick-receive" data-quantity="1" data-uom="CS">Receive 1 CS</button>
|
||||||
|
<div>
|
||||||
|
## TODO: probably should make these optional / configurable
|
||||||
|
<button type="button" class="quick-receive ui-btn ui-btn-inline ui-corner-all" data-quantity="1" data-uom="EA">1 EA</button>
|
||||||
|
<button type="button" class="quick-receive ui-btn ui-btn-inline ui-corner-all" data-quantity="3" data-uom="EA">3 EA</button>
|
||||||
|
<button type="button" class="quick-receive ui-btn ui-btn-inline ui-corner-all" data-quantity="6" data-uom="EA">6 EA</button>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
% else:
|
||||||
|
<button type="button" class="quick-receive" data-quantity="1" data-uom="${unit_uom}">Receive 1 ${unit_uom}</button>
|
||||||
|
% endif
|
||||||
|
% endif
|
||||||
|
|
||||||
|
${keypad(unit_uom, uom, allow_cases=allow_cases)}
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<fieldset data-role="controlgroup" data-type="horizontal" class="receiving-mode">
|
||||||
|
${h.radio('mode', value='received', label="received", checked=True)}
|
||||||
|
${h.radio('mode', value='damaged', label="damaged")}
|
||||||
|
% if allow_expired:
|
||||||
|
${h.radio('mode', value='expired', label="expired")}
|
||||||
|
% endif
|
||||||
|
</fieldset>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="expiration-row" style="display: none;">
|
||||||
|
<td>
|
||||||
|
<div style="padding:10px 20px;">
|
||||||
|
<label for="expiration_date">Expiration Date</label>
|
||||||
|
<input name="expiration_date" type="date" value="" placeholder="YYYY-MM-DD" />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<fieldset data-role="controlgroup" data-type="horizontal" class="receiving-actions">
|
||||||
|
<button type="button" data-action="add" class="ui-btn-inline ui-corner-all">Add</button>
|
||||||
|
<button type="button" data-action="subtract" class="ui-btn-inline ui-corner-all">Subtract</button>
|
||||||
|
## <button type="button" data-action="clear" class="ui-btn-inline ui-corner-all ui-state-disabled">Clear</button>
|
||||||
|
</fieldset>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
${h.hidden('quick_receive', value='false')}
|
||||||
|
${h.end_form()}
|
||||||
|
|
||||||
|
% if master.mobile_rows_deletable and master.row_deletable(row) and request.has_perm('{}.delete_row'.format(permission_prefix)):
|
||||||
|
${h.form(url('mobile.{}.delete_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='receiving-update')}
|
||||||
|
${h.csrf_token(request)}
|
||||||
|
${h.submit('submit', "Delete this Row")}
|
||||||
|
${h.end_form()}
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% endif
|
|
@ -1115,6 +1115,15 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
description = row.product.full_description if row.product else row.description
|
description = row.product.full_description if row.product else row.description
|
||||||
return "({}) {}".format(key, description)
|
return "({}) {}".format(key, description)
|
||||||
|
|
||||||
|
def make_mobile_row_grid_kwargs(self, **kwargs):
|
||||||
|
kwargs = super(ReceivingBatchView, self).make_mobile_row_grid_kwargs(**kwargs)
|
||||||
|
|
||||||
|
# use custom `receive_row` instead of `view_row`
|
||||||
|
# TODO: should still use `view_row` in some cases? e.g. executed batch
|
||||||
|
kwargs['url'] = lambda obj: self.get_row_action_url('receive', obj, mobile=True)
|
||||||
|
|
||||||
|
return kwargs
|
||||||
|
|
||||||
def should_aggregate_products(self, batch):
|
def should_aggregate_products(self, batch):
|
||||||
"""
|
"""
|
||||||
Must return a boolean indicating whether rows should be aggregated by
|
Must return a boolean indicating whether rows should be aggregated by
|
||||||
|
@ -1395,6 +1404,120 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
||||||
return self.render_to_response('view_row', context, mobile=True)
|
return self.render_to_response('view_row', context, mobile=True)
|
||||||
|
|
||||||
|
def mobile_receive_row(self):
|
||||||
|
"""
|
||||||
|
Mobile view for row-level receiving.
|
||||||
|
"""
|
||||||
|
self.mobile = True
|
||||||
|
self.viewing = True
|
||||||
|
row = self.get_row_instance()
|
||||||
|
batch = row.batch
|
||||||
|
permission_prefix = self.get_permission_prefix()
|
||||||
|
form = self.make_mobile_row_form(row)
|
||||||
|
context = {
|
||||||
|
'row': row,
|
||||||
|
'batch': batch,
|
||||||
|
'parent_instance': batch,
|
||||||
|
'instance': row,
|
||||||
|
'instance_title': self.get_row_instance_title(row),
|
||||||
|
'parent_model_title': self.get_model_title(),
|
||||||
|
'product_image_url': self.get_row_image_url(row),
|
||||||
|
'form': form,
|
||||||
|
'allow_expired': self.handler.allow_expired_credits(),
|
||||||
|
'allow_cases': self.handler.allow_cases(),
|
||||||
|
'quick_receive': False,
|
||||||
|
'quick_receive_all': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
|
||||||
|
default=True)
|
||||||
|
if batch.order_quantities_known:
|
||||||
|
context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
if self.request.has_perm('{}.create_row'.format(permission_prefix)):
|
||||||
|
schema = MobileReceivingForm().bind(session=self.Session())
|
||||||
|
update_form = forms.Form(schema=schema, request=self.request)
|
||||||
|
if update_form.validate(newstyle=True):
|
||||||
|
row = self.Session.query(model.PurchaseBatchRow).get(update_form.validated['row'])
|
||||||
|
mode = update_form.validated['mode']
|
||||||
|
cases = update_form.validated['cases']
|
||||||
|
units = update_form.validated['units']
|
||||||
|
|
||||||
|
# handler takes care of the row receiving logic for us
|
||||||
|
kwargs = dict(update_form.validated)
|
||||||
|
del kwargs['row']
|
||||||
|
self.handler.receive_row(row, **kwargs)
|
||||||
|
|
||||||
|
# keep track of last-used uom, although we just track
|
||||||
|
# whether or not it was 'CS' since the unit_uom can vary
|
||||||
|
sticky_case = None
|
||||||
|
if not update_form.validated['quick_receive']:
|
||||||
|
if cases and not units:
|
||||||
|
sticky_case = True
|
||||||
|
elif units and not cases:
|
||||||
|
sticky_case = False
|
||||||
|
if sticky_case is not None:
|
||||||
|
self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
|
||||||
|
|
||||||
|
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||||
|
|
||||||
|
# unit_uom can vary by product
|
||||||
|
context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
|
||||||
|
|
||||||
|
if context['quick_receive'] and context['quick_receive_all']:
|
||||||
|
if context['allow_cases']:
|
||||||
|
context['quick_receive_uom'] = 'CS'
|
||||||
|
raise NotImplementedError("TODO: add CS support for quick_receive_all")
|
||||||
|
else:
|
||||||
|
context['quick_receive_uom'] = context['unit_uom']
|
||||||
|
accounted_for = self.handler.get_units_accounted_for(row)
|
||||||
|
remainder = self.handler.get_units_ordered(row) - accounted_for
|
||||||
|
|
||||||
|
if accounted_for:
|
||||||
|
# some product accounted for; button should receive "remainder" only
|
||||||
|
if remainder:
|
||||||
|
remainder = pretty_quantity(remainder)
|
||||||
|
context['quick_receive_quantity'] = remainder
|
||||||
|
context['quick_receive_text'] = "Receive Remainder ({} {})".format(remainder, context['unit_uom'])
|
||||||
|
else:
|
||||||
|
# unless there is no remainder, in which case disable it
|
||||||
|
context['quick_receive'] = False
|
||||||
|
|
||||||
|
else: # nothing yet accounted for, button should receive "all"
|
||||||
|
if not remainder:
|
||||||
|
raise ValueError("why is remainder empty?")
|
||||||
|
remainder = pretty_quantity(remainder)
|
||||||
|
context['quick_receive_quantity'] = remainder
|
||||||
|
context['quick_receive_text'] = "Receive ALL ({} {})".format(remainder, context['unit_uom'])
|
||||||
|
|
||||||
|
# effective uom can vary in a few ways...the basic default is 'CS' if
|
||||||
|
# self.default_uom_is_case is true, otherwise whatever unit_uom is.
|
||||||
|
sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
|
||||||
|
if sticky_case is None:
|
||||||
|
context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
|
||||||
|
elif sticky_case:
|
||||||
|
context['uom'] = 'CS'
|
||||||
|
else:
|
||||||
|
context['uom'] = context['unit_uom']
|
||||||
|
if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
|
||||||
|
context['uom'] = context['unit_uom']
|
||||||
|
|
||||||
|
if batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
|
||||||
|
warn = True
|
||||||
|
if batch.is_truck_dump_parent() and row.product:
|
||||||
|
uuids = [child.uuid for child in batch.truck_dump_children]
|
||||||
|
if uuids:
|
||||||
|
count = self.Session.query(model.PurchaseBatchRow)\
|
||||||
|
.filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
|
||||||
|
.filter(model.PurchaseBatchRow.product == row.product)\
|
||||||
|
.count()
|
||||||
|
if count:
|
||||||
|
warn = False
|
||||||
|
if warn:
|
||||||
|
self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
|
||||||
|
return self.render_to_response('receive_row', context, mobile=True)
|
||||||
|
|
||||||
def auto_receive(self):
|
def auto_receive(self):
|
||||||
"""
|
"""
|
||||||
View which can "auto-receive" all items in the batch. Meant only as a
|
View which can "auto-receive" all items in the batch. Meant only as a
|
||||||
|
@ -1455,6 +1578,11 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
model_key = cls.get_model_key()
|
model_key = cls.get_model_key()
|
||||||
permission_prefix = cls.get_permission_prefix()
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
|
||||||
|
# row-level receiving
|
||||||
|
config.add_route('mobile.{}.receive_row'.format(route_prefix), '/mobile{}/{{uuid}}/rows/{{row_uuid}}/receive'.format(url_prefix))
|
||||||
|
config.add_view(cls, attr='mobile_receive_row', route_name='mobile.{}.receive_row'.format(route_prefix),
|
||||||
|
permission='{}.edit_row'.format(permission_prefix))
|
||||||
|
|
||||||
if cls.allow_truck_dump:
|
if cls.allow_truck_dump:
|
||||||
|
|
||||||
# add TD child batch, from invoice file
|
# add TD child batch, from invoice file
|
||||||
|
|
Loading…
Reference in a new issue