Add support for variance inventory batches, aggregation by product
kind of a rushed job but hopefully this is all good...
This commit is contained in:
parent
5bc4a1618b
commit
db645fb393
|
@ -101,7 +101,13 @@
|
||||||
$('#description').val(data.product.description);
|
$('#description').val(data.product.description);
|
||||||
$('#size').val(data.product.size);
|
$('#size').val(data.product.size);
|
||||||
$('#case_quantity').val(data.product.case_quantity);
|
$('#case_quantity').val(data.product.case_quantity);
|
||||||
if (data.product.type2) {
|
|
||||||
|
if (data.already_present_in_batch) {
|
||||||
|
$('#product-info .warning.present').show();
|
||||||
|
$('#cases').val(data.cases);
|
||||||
|
$('#units').val(data.units);
|
||||||
|
|
||||||
|
} else if (data.product.type2) {
|
||||||
$('#units').val(data.product.units);
|
$('#units').val(data.product.units);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,11 +123,18 @@
|
||||||
% endif
|
% 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:
|
% if allow_cases:
|
||||||
|
if ($('#cases').val()) {
|
||||||
$('#cases').focus().select();
|
$('#cases').focus().select();
|
||||||
|
} else if ($('#units').val()) {
|
||||||
|
$('#units').focus().select();
|
||||||
|
} else {
|
||||||
|
$('#cases').focus().select();
|
||||||
|
}
|
||||||
% else:
|
% else:
|
||||||
$('#units').focus().select();
|
$('#units').focus().select();
|
||||||
% endif
|
% endif
|
||||||
|
@ -222,6 +235,7 @@
|
||||||
<p>please ENTER a scancode</p>
|
<p>please ENTER a scancode</p>
|
||||||
<div class="img-wrapper"><img /></div>
|
<div class="img-wrapper"><img /></div>
|
||||||
<div class="warning notfound">please confirm UPC and provide more details</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,6 +27,7 @@ Views for inventory batches
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import decimal
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -63,6 +64,10 @@ class InventoryAdjustmentReasonsView(MasterView):
|
||||||
'description',
|
'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
super(InventoryAdjustmentReasonsView, self).configure_grid(g)
|
||||||
|
g.set_sort_defaults('code')
|
||||||
|
|
||||||
def configure_form(self, f):
|
def configure_form(self, f):
|
||||||
super(InventoryAdjustmentReasonsView, self).configure_form(f)
|
super(InventoryAdjustmentReasonsView, self).configure_form(f)
|
||||||
|
|
||||||
|
@ -296,6 +301,16 @@ class InventoryBatchView(BatchMasterView):
|
||||||
if form.validate(newstyle=True):
|
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
|
||||||
|
if self.should_aggregate_products(batch):
|
||||||
|
row = self.find_row_for_product(batch, product)
|
||||||
|
if row:
|
||||||
|
row.cases = form.validated['cases']
|
||||||
|
row.units = form.validated['units']
|
||||||
|
self.handler.refresh_row(row)
|
||||||
|
|
||||||
|
if not row:
|
||||||
row = model.InventoryBatchRow()
|
row = model.InventoryBatchRow()
|
||||||
row.product = product
|
row.product = product
|
||||||
row.upc = form.validated['upc']
|
row.upc = form.validated['upc']
|
||||||
|
@ -307,6 +322,7 @@ class InventoryBatchView(BatchMasterView):
|
||||||
row.units = form.validated['units']
|
row.units = form.validated['units']
|
||||||
self.handler.capture_current_units(row)
|
self.handler.capture_current_units(row)
|
||||||
self.handler.add_row(batch, row)
|
self.handler.add_row(batch, row)
|
||||||
|
|
||||||
description = make_full_description(form.validated['brand_name'],
|
description = make_full_description(form.validated['brand_name'],
|
||||||
form.validated['description'],
|
form.validated['description'],
|
||||||
form.validated['size'])
|
form.validated['size'])
|
||||||
|
@ -334,6 +350,15 @@ class InventoryBatchView(BatchMasterView):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def should_aggregate_products(self, batch):
|
||||||
|
"""
|
||||||
|
Must return a boolean indicating whether rows should be aggregated by
|
||||||
|
product for the given batch.
|
||||||
|
"""
|
||||||
|
if batch.mode == self.enum.INVENTORY_MODE_VARIANCE:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
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
|
||||||
|
@ -345,12 +370,25 @@ class InventoryBatchView(BatchMasterView):
|
||||||
'error': "Current batch has already been executed",
|
'error': "Current batch has already been executed",
|
||||||
'redirect': self.get_action_url('view', batch),
|
'redirect': self.get_action_url('view', batch),
|
||||||
}
|
}
|
||||||
data = {}
|
|
||||||
entry = self.request.GET.get('upc', '')
|
entry = self.request.GET.get('upc', '')
|
||||||
product = self.find_product(entry)
|
aggregate = self.should_aggregate_products(batch)
|
||||||
data = self.product_info(product)
|
|
||||||
|
|
||||||
result = {'product': data or None, 'upc_raw': entry, 'upc': None}
|
type2 = self.find_type2_product(entry)
|
||||||
|
if type2:
|
||||||
|
product, price = type2
|
||||||
|
else:
|
||||||
|
product = self.find_product(entry)
|
||||||
|
|
||||||
|
data = self.product_info(product)
|
||||||
|
if type2:
|
||||||
|
data['type2'] = True
|
||||||
|
if not aggregate:
|
||||||
|
if price is None:
|
||||||
|
data['units'] = 1
|
||||||
|
else:
|
||||||
|
data['units'] = float((price / product.regular_price.price).quantize(decimal.Decimal('0.01')))
|
||||||
|
|
||||||
|
result = {'product': data, 'upc_raw': entry, 'upc': None}
|
||||||
if not data:
|
if not data:
|
||||||
upc = re.sub(r'\D', '', entry.strip())
|
upc = re.sub(r'\D', '', entry.strip())
|
||||||
if upc:
|
if upc:
|
||||||
|
@ -358,8 +396,28 @@ class InventoryBatchView(BatchMasterView):
|
||||||
result['upc'] = six.text_type(upc)
|
result['upc'] = six.text_type(upc)
|
||||||
result['upc_pretty'] = upc.pretty()
|
result['upc_pretty'] = upc.pretty()
|
||||||
result['image_url'] = pod.get_image_url(self.rattail_config, upc)
|
result['image_url'] = pod.get_image_url(self.rattail_config, upc)
|
||||||
|
|
||||||
|
if product and aggregate:
|
||||||
|
row = self.find_row_for_product(batch, product)
|
||||||
|
if row:
|
||||||
|
result['already_present_in_batch'] = True
|
||||||
|
result['cases'] = float(row.cases) if row.cases is not None else None
|
||||||
|
result['units'] = float(row.units) if row.units is not None else None
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def find_row_for_product(self, batch, product):
|
||||||
|
rows = self.Session.query(model.InventoryBatchRow)\
|
||||||
|
.filter(model.InventoryBatchRow.batch == batch)\
|
||||||
|
.filter(model.InventoryBatchRow.product == product)\
|
||||||
|
.filter(model.InventoryBatchRow.removed == False)\
|
||||||
|
.all()
|
||||||
|
if rows:
|
||||||
|
if len(rows) > 1:
|
||||||
|
log.error("inventory batch %s should aggregate products, but has %s rows for: %s",
|
||||||
|
batch.id_str, len(rows), product)
|
||||||
|
return rows[0]
|
||||||
|
|
||||||
def find_product(self, entry):
|
def find_product(self, entry):
|
||||||
upc = re.sub(r'\D', '', entry.strip())
|
upc = re.sub(r'\D', '', entry.strip())
|
||||||
if upc:
|
if upc:
|
||||||
|
@ -426,40 +484,57 @@ class InventoryBatchView(BatchMasterView):
|
||||||
"""
|
"""
|
||||||
batch = self.get_instance()
|
batch = self.get_instance()
|
||||||
row = None
|
row = None
|
||||||
upc = self.request.GET.get('upc', '').strip()
|
entry = self.request.GET.get('upc', '').strip()
|
||||||
upc = re.sub(r'\D', '', upc)
|
entry = re.sub(r'\D', '', entry)
|
||||||
if upc:
|
if entry:
|
||||||
|
|
||||||
if len(upc) <= 14:
|
if len(entry) <= 14:
|
||||||
row = self.add_row_for_upc(batch, upc)
|
row = self.add_row_for_upc(batch, entry, warn_if_present=True)
|
||||||
if not row:
|
if not row:
|
||||||
self.request.session.flash("Product not found: {}".format(upc), 'error')
|
self.request.session.flash("Product not found: {}".format(entry), 'error')
|
||||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.request.session.flash("UPC has too many digits ({}): {}".format(len(upc), upc), 'error')
|
self.request.session.flash("UPC has too many digits ({}): {}".format(len(entry), entry), 'error')
|
||||||
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
return self.redirect(self.get_action_url('view', batch, mobile=True))
|
||||||
|
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
return self.redirect(self.mobile_row_route_url('view', uuid=row.batch_uuid, row_uuid=row.uuid))
|
return self.redirect(self.mobile_row_route_url('view', uuid=row.batch_uuid, row_uuid=row.uuid))
|
||||||
|
|
||||||
def add_row_for_upc(self, batch, upc):
|
def add_row_for_upc(self, batch, entry, warn_if_present=False):
|
||||||
"""
|
"""
|
||||||
Add a row to the batch for the given UPC, if applicable.
|
Add a row to the batch for the given UPC, if applicable.
|
||||||
"""
|
"""
|
||||||
# try to locate general product by UPC; add to batch either way
|
type2 = self.find_type2_product(entry)
|
||||||
provided = GPC(upc, calc_check_digit=False)
|
if type2:
|
||||||
checked = GPC(upc, calc_check_digit='upc')
|
product, price = type2
|
||||||
product = api.get_product_by_upc(self.Session(), provided)
|
else:
|
||||||
if not product:
|
product = self.find_product(entry)
|
||||||
product = api.get_product_by_upc(self.Session(), checked)
|
|
||||||
if product or self.unknown_product_creates_row:
|
|
||||||
row = model.InventoryBatchRow()
|
|
||||||
if product:
|
if product:
|
||||||
|
|
||||||
|
aggregate = self.should_aggregate_products(batch)
|
||||||
|
if aggregate:
|
||||||
|
row = self.find_row_for_product(batch, product)
|
||||||
|
if row:
|
||||||
|
if warn_if_present:
|
||||||
|
self.request.session.flash("Product already exists in batch; please confirm counts", 'error')
|
||||||
|
return row
|
||||||
|
|
||||||
|
row = model.InventoryBatchRow()
|
||||||
row.product = product
|
row.product = product
|
||||||
row.upc = product.upc
|
row.upc = product.upc
|
||||||
|
self.handler.capture_current_units(row)
|
||||||
|
if type2 and not aggregate:
|
||||||
|
if price is None:
|
||||||
|
row.units = 1
|
||||||
else:
|
else:
|
||||||
row.upc = provided # TODO: why not 'checked' instead? how to choose?
|
row.units = (price / product.regular_price.price).quantize(decimal.Decimal('0.01'))
|
||||||
|
self.handler.add_row(batch, row)
|
||||||
|
return row
|
||||||
|
|
||||||
|
elif self.unknown_product_creates_row:
|
||||||
|
row = model.InventoryBatchRow()
|
||||||
|
row.upc = GPC(upc, calc_check_digit=False) # TODO: why not calc check digit?
|
||||||
row.description = "(unknown product)"
|
row.description = "(unknown product)"
|
||||||
self.handler.capture_current_units(row)
|
self.handler.capture_current_units(row)
|
||||||
self.handler.add_row(batch, row)
|
self.handler.add_row(batch, row)
|
||||||
|
|
Loading…
Reference in a new issue