Refactor mobile receiving to use "quick row" feature

plus some other random things thrown in there, for good measure..
This commit is contained in:
Lance Edgar 2018-07-16 20:40:29 -05:00
parent 3cc8adba86
commit a34a42d2b2
9 changed files with 177 additions and 156 deletions

View file

@ -50,6 +50,10 @@ class PurchasingBatchView(BatchMasterView):
supports_new_product = False
cloneable = True
labels = {
'po_total': "PO Total",
}
grid_columns = [
'id',
'vendor',
@ -214,6 +218,16 @@ class PurchasingBatchView(BatchMasterView):
# form = super(PurchasingBatchView, self).make_form(batch, **kwargs)
# return form
def configure_common_form(self, f):
super(PurchasingBatchView, self).configure_common_form(f)
# po_total
if self.creating:
f.remove_field('po_total')
else:
f.set_readonly('po_total')
f.set_type('po_total', 'currency')
def configure_form(self, f):
super(PurchasingBatchView, self).configure_form(f)
batch = f.model_instance
@ -329,11 +343,6 @@ class PurchasingBatchView(BatchMasterView):
# po_number
f.set_label('po_number', "PO Number")
# po_total
f.set_readonly('po_total')
f.set_type('po_total', 'currency')
f.set_label('po_total', "PO Total")
# invoice_total
f.set_readonly('invoice_total')
f.set_type('invoice_total', 'currency')
@ -364,12 +373,6 @@ class PurchasingBatchView(BatchMasterView):
'vendor_contact',
'status_code')
def configure_mobile_form(self, f):
super(PurchasingBatchView, self).configure_mobile_form(f)
# currency fields
f.set_type('po_total', 'currency')
def render_store(self, batch, field):
store = batch.store
if not store:

View file

@ -104,6 +104,7 @@ class ReceivingBatchView(PurchasingBatchView):
mobile_creatable = True
mobile_rows_filterable = True
mobile_rows_creatable = True
mobile_rows_quickable = True
allow_from_po = False
allow_from_scratch = True
@ -351,7 +352,17 @@ class ReceivingBatchView(PurchasingBatchView):
def get_batch_kwargs(self, batch, mobile=False):
kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
if not mobile:
if mobile:
purchase = self.get_purchase(self.request.POST['purchase'])
if isinstance(purchase, model.Purchase):
kwargs['purchase'] = purchase
department = self.department_for_purchase(purchase)
if department:
kwargs['department'] = department
else: # not mobile
batch_type = self.request.POST['batch_type']
if batch_type == 'from_scratch':
kwargs.pop('truck_dump_batch', None)
@ -365,6 +376,9 @@ class ReceivingBatchView(PurchasingBatchView):
raise NotImplementedError
return kwargs
def department_for_purchase(self, purchase):
pass
def delete_instance(self, batch):
"""
Delete all data (files etc.) for the batch.
@ -498,6 +512,9 @@ class ReceivingBatchView(PurchasingBatchView):
default_value=default_status)
return filters
def get_purchase(self, uuid):
return self.Session.query(model.Purchase).get(uuid)
def mobile_create(self):
"""
Mobile view for creating a new receiving batch
@ -580,8 +597,9 @@ class ReceivingBatchView(PurchasingBatchView):
f.set_readonly('invoice_total')
def render_mobile_row_listitem(self, row, i):
key = self.render_product_key_value(row)
description = row.product.full_description if row.product else row.description
return "({}) {}".format(row.upc.pretty(), description)
return "({}) {}".format(key, description)
def should_aggregate_products(self, batch):
"""
@ -590,72 +608,79 @@ class ReceivingBatchView(PurchasingBatchView):
"""
return True
# TODO: this view can create new rows, with only a GET query. that should
# probably be changed to require POST; for now we just require the "create
# batch row" perm and call it good..
def mobile_lookup(self):
"""
Locate and/or create a row within the batch, according to the given
product UPC, then redirect to the row view page.
"""
batch = self.get_instance()
row = None
upc = self.request.GET.get('upc', '').strip()
upc = re.sub(r'\D', '', upc)
if not upc:
self.request.session.flash("Invalid UPC: {}".format(self.request.GET.get('upc')), 'error')
return self.redirect(self.get_action_url('view', batch, mobile=True))
# first try to locate existing batch row by UPC match
provided = GPC(upc, calc_check_digit=False)
checked = GPC(upc, calc_check_digit='upc')
rows = self.Session.query(model.PurchaseBatchRow)\
.filter(model.PurchaseBatchRow.batch == batch)\
.filter(model.PurchaseBatchRow.upc.in_((provided, checked)))\
.filter(model.PurchaseBatchRow.removed == False)\
.all()
def quick_locate_rows(self, batch, entry):
rows = []
# we prefer "exact" matches, i.e. those which assumed the entry already
# contained the check digit.
provided = GPC(entry, calc_check_digit=False)
for row in batch.active_rows():
if row.upc == provided:
rows.append(row)
if rows:
if self.should_aggregate_products(batch):
if len(rows) > 1:
log.warning("found multiple UPC matches for {} in batch {}: {}".format(
upc, batch.id_str, batch))
row = rows[0]
return rows
else:
# if no "exact" matches, we'll settle for those which assume the entry
# lacked a check digit.
checked = GPC(entry, calc_check_digit='upc')
for row in batch.active_rows():
if row.upc == checked:
rows.append(row)
return rows
def save_quick_row_form(self, form):
batch = self.get_instance()
entry = form.validated['quick_row_entry']
# maybe try to locate existing row first
rows = self.quick_locate_rows(batch, entry)
if rows:
# if aggregating, just re-use matching row
prefer_existing = self.should_aggregate_products(batch)
if prefer_existing:
if len(rows) > 1:
log.warning("found multiple row matches for '%s' in batch %s: %s",
entry, batch.id_str, batch)
return rows[0]
else: # borrow product from matching row, but make new row
other_row = rows[0]
row = model.PurchaseBatchRow()
row.product = other_row.product
self.handler.add_row(batch, row)
# TODO: is this necessary here? is so, then what about further below?
# self.handler.refresh_batch_status(batch)
self.Session.flush()
return row
else:
# try to locate product by upc
provided = GPC(entry, calc_check_digit=False)
checked = GPC(entry, calc_check_digit='upc')
product = api.get_product_by_upc(self.Session(), provided)
if not product:
product = api.get_product_by_upc(self.Session(), checked)
if product:
row = model.PurchaseBatchRow()
row.product = product
self.handler.add_row(batch, row)
self.Session.flush()
return row
# try to locate general product by UPC; add to batch if found
product = api.get_product_by_upc(self.Session(), provided)
if not product:
product = api.get_product_by_upc(self.Session(), checked)
if product:
row = model.PurchaseBatchRow()
row.product = product
self.handler.add_row(batch, row)
# check for "bad" upc
elif len(upc) > 14:
self.request.session.flash("Invalid UPC: {}".format(upc), 'error')
return self.redirect(self.get_action_url('view', batch, mobile=True))
# product in system, but sane upc, so add to batch anyway
else:
row = model.PurchaseBatchRow()
row.upc = provided # TODO: why not checked? how to know?
row.description = "(unknown product)"
self.handler.add_row(batch, row)
self.handler.refresh_batch_status(batch)
# check for "bad" upc
if len(entry) > 14:
return
# product not in system, but presumably sane upc, so add to batch anyway
row = model.PurchaseBatchRow()
row.upc = provided # TODO: why not checked? how to know?
row.description = "(unknown product)"
self.handler.add_row(batch, row)
self.Session.flush()
return self.redirect(self.mobile_row_route_url('view', uuid=row.batch_uuid, row_uuid=row.uuid))
return row
def redirect_after_quick_row(self, row, mobile=False):
if mobile:
return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
return super(ReceivingBatchView, self).redirect_after_quick_row(row, mobile=mobile)
def mobile_view_row(self):
"""
@ -806,11 +831,6 @@ class ReceivingBatchView(PurchasingBatchView):
model_key = cls.get_model_key()
permission_prefix = cls.get_permission_prefix()
# mobile lookup (note perm; this view can create new rows)
config.add_route('mobile.{}.lookup'.format(route_prefix), '/mobile{}/{{{}}}/lookup'.format(url_prefix, model_key))
config.add_view(cls, attr='mobile_lookup', route_name='mobile.{}.lookup'.format(route_prefix),
renderer='json', permission='{}.create_row'.format(permission_prefix))
if cls.allow_truck_dump:
config.add_route('{}.add_child_from_invoice'.format(route_prefix), '{}/{{{}}}/add-child-from-invoice'.format(url_prefix, model_key))
config.add_view(cls, attr='add_child_from_invoice', route_name='{}.add_child_from_invoice'.format(route_prefix),