Overhaul the Receiving Form to account for "product not found" etc.

Also shows ordered/received/etc. quantities
This commit is contained in:
Lance Edgar 2016-12-13 22:28:50 -06:00
parent acbb3d289c
commit ed252c6465
2 changed files with 245 additions and 53 deletions

View file

@ -33,10 +33,11 @@ from sqlalchemy import orm
from rattail import pod
from rattail.db import model, api
from rattail.db.util import make_full_description
from rattail.gpc import GPC
from rattail.time import localtime
from rattail.core import Object
from rattail.util import OrderedDict
from rattail.util import OrderedDict, pretty_quantity
import formalchemy as fa
import formencode as fe
@ -55,8 +56,13 @@ class ReceivingForm(forms.Schema):
filter_extra_fields = True
mode = fe.validators.OneOf(['received', 'damaged', 'expired', 'mispick'])
product = forms.validators.ValidProduct()
cases = fe.validators.Int()
units = fe.validators.Int()
upc = forms.validators.ValidGPC()
brand_name = fe.validators.String()
description = fe.validators.String()
size = fe.validators.String()
case_quantity = fe.validators.Number()
cases = fe.validators.Number()
units = fe.validators.Number()
expiration_date = fe.validators.DateValidator()
ordered_product = forms.validators.ValidProduct()
@ -361,8 +367,10 @@ class PurchaseBatchView(BatchMasterView):
def row_grid_row_attrs(self, row, i):
attrs = {}
if row.status_code in (row.STATUS_INCOMPLETE,
row.STATUS_ORDERED_RECEIVED_DIFFER):
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
attrs['class_'] = 'warning'
elif row.status_code in (row.STATUS_INCOMPLETE,
row.STATUS_ORDERED_RECEIVED_DIFFER):
attrs['class_'] = 'notice'
return attrs
@ -409,6 +417,9 @@ class PurchaseBatchView(BatchMasterView):
fs.item_lookup,
fs.upc,
fs.product,
fs.brand_name,
fs.description,
fs.size,
fs.case_quantity,
fs.cases_ordered,
fs.units_ordered,
@ -450,6 +461,12 @@ class PurchaseBatchView(BatchMasterView):
elif self.viewing:
del fs.item_lookup
if fs.model.product:
del (fs.brand_name,
fs.description,
fs.size)
else:
del fs.product
def before_create_row(self, form):
row = form.fieldset.model
@ -615,8 +632,8 @@ class PurchaseBatchView(BatchMasterView):
if row.po_total and not row.removed:
batch.po_total -= row.po_total
if cases_ordered or units_ordered:
row.cases_ordered = cases_ordered
row.units_ordered = units_ordered
row.cases_ordered = cases_ordered or None
row.units_ordered = units_ordered or None
row.removed = False
self.handler.refresh_row(row)
else:
@ -627,13 +644,13 @@ class PurchaseBatchView(BatchMasterView):
row.sequence = max([0] + [r.sequence for r in batch.data_rows]) + 1
row.product = product
batch.data_rows.append(row)
row.cases_ordered = cases_ordered
row.units_ordered = units_ordered
row.cases_ordered = cases_ordered or None
row.units_ordered = units_ordered or None
self.handler.refresh_row(row)
return {
'row_cases_ordered': '' if row.removed else int(row.cases_ordered),
'row_units_ordered': '' if row.removed else int(row.units_ordered),
'row_cases_ordered': '' if row.removed else int(row.cases_ordered or 0),
'row_units_ordered': '' if row.removed else int(row.units_ordered or 0),
'row_po_total': '' if row.removed else '${:0,.2f}'.format(row.po_total),
'batch_po_total': '${:0,.2f}'.format(batch.po_total),
}
@ -689,7 +706,11 @@ class PurchaseBatchView(BatchMasterView):
mode = form.data['mode']
shipped_product = form.data['product']
product = form.data['ordered_product'] if mode == 'mispick' else shipped_product
rows = [row for row in batch.active_rows() if row.product is product]
if product:
rows = [row for row in batch.active_rows() if row.product is product]
else:
upc = form.data['upc']
rows = [row for row in batch.active_rows() if not row.product and row.upc == upc]
if rows:
if len(rows) > 1:
log.warning("found {} matching rows in batch {} for product: {}".format(
@ -698,6 +719,11 @@ class PurchaseBatchView(BatchMasterView):
else:
row = model.PurchaseBatchRow()
row.product = product
row.upc = form.data['upc']
row.brand_name = form.data['brand_name']
row.description = form.data['description']
row.size = form.data['size']
row.case_quantity = form.data['case_quantity']
batch.add_row(row)
cases = form.data['cases']
@ -716,9 +742,12 @@ class PurchaseBatchView(BatchMasterView):
self.handler.refresh_row(row)
description = make_full_description(form.data['brand_name'],
form.data['description'],
form.data['size'])
self.request.session.flash("({}) {} cases, {} units: {} {}".format(
form.data['mode'], form.data['cases'] or 0, form.data['units'] or 0,
product.upc.pretty(), product))
form.data['upc'].pretty(), description))
return self.redirect(self.request.current_route_url())
title = self.get_instance_title(batch)
@ -743,35 +772,78 @@ class PurchaseBatchView(BatchMasterView):
'error': "Current batch has already been executed",
'redirect': self.get_action_url('view', batch),
}
data = None
data = {}
upc = self.request.GET.get('upc', '').strip()
upc = re.sub(r'\D', '', upc)
if upc:
product = api.get_product_by_upc(Session(), upc)
if not product:
# Try again, assuming caller did not include check digit.
upc = GPC(upc, calc_check_digit='upc')
product = api.get_product_by_upc(Session(), upc)
if product and (not product.deleted or self.request.has_perm('products.view_deleted')):
data = {
'uuid': product.uuid,
'upc': unicode(product.upc),
'upc_pretty': product.upc.pretty(),
'full_description': product.full_description,
'image_url': pod.get_image_url(self.rattail_config, product.upc),
}
cost = product.cost_for_vendor(batch.vendor)
if cost:
data['cost_found'] = True
if int(cost.case_size) == cost.case_size:
data['cost_case_size'] = int(cost.case_size)
# 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 = Session.query(model.PurchaseBatchRow)\
.filter(model.PurchaseBatchRow.batch == batch)\
.filter(model.PurchaseBatchRow.upc.in_((provided, checked)))\
.all()
if rows:
if len(rows) > 1:
log.warning("found multiple UPC matches for {} in batch {}: {}".format(
upc, batch.id_str, batch))
row = rows[0]
data['uuid'] = row.product_uuid
data['upc'] = unicode(row.upc)
data['upc_pretty'] = row.upc.pretty()
data['full_description'] = make_full_description(row.brand_name, row.description, row.size)
data['brand_name'] = row.brand_name
data['description'] = row.description
data['size'] = row.size
data['case_quantity'] = pretty_quantity(row.case_quantity)
data['image_url'] = pod.get_image_url(self.rattail_config, row.upc)
data['found_in_batch'] = True
data['cases_ordered'] = pretty_quantity(row.cases_ordered, empty_zero=True)
data['units_ordered'] = pretty_quantity(row.units_ordered, empty_zero=True)
data['cases_received'] = pretty_quantity(row.cases_received, empty_zero=True)
data['units_received'] = pretty_quantity(row.units_received, empty_zero=True)
data['cases_damaged'] = pretty_quantity(row.cases_damaged, empty_zero=True)
data['units_damaged'] = pretty_quantity(row.units_damaged, empty_zero=True)
data['cases_expired'] = pretty_quantity(row.cases_expired, empty_zero=True)
data['units_expired'] = pretty_quantity(row.units_expired, empty_zero=True)
data['cases_mispick'] = pretty_quantity(row.cases_mispick, empty_zero=True)
data['units_mispick'] = pretty_quantity(row.units_mispick, empty_zero=True)
else: # no match in our batch, do full product search
product = api.get_product_by_upc(Session(), provided)
if not product:
product = api.get_product_by_upc(Session(), checked)
if product and (not product.deleted or self.request.has_perm('products.view_deleted')):
data['uuid'] = product.uuid
data['upc'] = unicode(product.upc)
data['upc_pretty'] = product.upc.pretty()
data['full_description'] = product.full_description
data['brand_name'] = unicode(product.brand or '')
data['description'] = product.description
data['size'] = product.size
data['case_quantity'] = 1 # default
cost = product.cost_for_vendor(batch.vendor)
if cost:
data['cost_found'] = True
data['cost_case_size'] = pretty_quantity(cost.case_size)
data['case_quantity'] = pretty_quantity(cost.case_size)
else:
data['cost_case_size'] = '{:0.4f}'.format(cost.case_size)
else:
data['cost_found'] = False
data['found_in_batch'] = product in [row.product for row in batch.active_rows()]
return {'product': data}
data['cost_found'] = False
data['image_url'] = pod.get_image_url(self.rattail_config, product.upc)
data['found_in_batch'] = product in [row.product for row in batch.active_rows()]
result = {'product': data or None, 'upc': None}
if not data and upc:
upc = GPC(upc)
result['upc'] = unicode(upc)
result['upc_pretty'] = upc.pretty()
result['image_url'] = pod.get_image_url(self.rattail_config, upc)
return result
def mobile_index(self):
self.mobile = True
return self.render_to_response('mobile_index', {})
@classmethod
def defaults(cls, config):
@ -781,6 +853,11 @@ class PurchaseBatchView(BatchMasterView):
model_key = cls.get_model_key()
model_title = cls.get_model_title()
# mobile
config.add_route('{}.mobile'.format(route_prefix), '/mobile{}'.format(url_prefix))
config.add_view(cls, attr='mobile_index', route_name='{}.mobile'.format(route_prefix),
permission='{}.list'.format(permission_prefix))
# eligible purchases (AJAX)
config.add_route('{}.eligible_purchases'.format(route_prefix), '{}/eligible-purchases'.format(url_prefix))
config.add_view(cls, attr='eligible_purchases', route_name='{}.eligible_purchases'.format(route_prefix),