diff --git a/tailbone/templates/batch/inventory/desktop_form.mako b/tailbone/templates/batch/inventory/desktop_form.mako
index 78740003..1b1ef0f2 100644
--- a/tailbone/templates/batch/inventory/desktop_form.mako
+++ b/tailbone/templates/batch/inventory/desktop_form.mako
@@ -101,7 +101,13 @@
$('#description').val(data.product.description);
$('#size').val(data.product.size);
$('#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);
}
@@ -117,11 +123,18 @@
% endif
$('.field-wrapper.units input').prop('disabled', false);
$('.buttons button').button('enable');
+
if (data.product.type2) {
$('#units').focus().select();
} else {
% if allow_cases:
- $('#cases').focus().select();
+ if ($('#cases').val()) {
+ $('#cases').focus().select();
+ } else if ($('#units').val()) {
+ $('#units').focus().select();
+ } else {
+ $('#cases').focus().select();
+ }
% else:
$('#units').focus().select();
% endif
@@ -222,6 +235,7 @@
please ENTER a scancode
please confirm UPC and provide more details
+ product already exists in batch, please confirm count
diff --git a/tailbone/views/inventory.py b/tailbone/views/inventory.py
index 9d13a553..5413fec1 100644
--- a/tailbone/views/inventory.py
+++ b/tailbone/views/inventory.py
@@ -27,6 +27,7 @@ Views for inventory batches
from __future__ import unicode_literals, absolute_import
import re
+import decimal
import logging
import six
@@ -63,6 +64,10 @@ class InventoryAdjustmentReasonsView(MasterView):
'description',
]
+ def configure_grid(self, g):
+ super(InventoryAdjustmentReasonsView, self).configure_grid(g)
+ g.set_sort_defaults('code')
+
def configure_form(self, f):
super(InventoryAdjustmentReasonsView, self).configure_form(f)
@@ -296,17 +301,28 @@ class InventoryBatchView(BatchMasterView):
if form.validate(newstyle=True):
product = self.Session.query(model.Product).get(form.validated['product'])
- row = model.InventoryBatchRow()
- row.product = product
- row.upc = form.validated['upc']
- row.brand_name = form.validated['brand_name']
- row.description = form.validated['description']
- row.size = form.validated['size']
- row.case_quantity = form.validated['case_quantity']
- row.cases = form.validated['cases']
- row.units = form.validated['units']
- self.handler.capture_current_units(row)
- self.handler.add_row(batch, row)
+
+ 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.product = product
+ row.upc = form.validated['upc']
+ row.brand_name = form.validated['brand_name']
+ row.description = form.validated['description']
+ row.size = form.validated['size']
+ row.case_quantity = form.validated['case_quantity']
+ row.cases = form.validated['cases']
+ row.units = form.validated['units']
+ self.handler.capture_current_units(row)
+ self.handler.add_row(batch, row)
+
description = make_full_description(form.validated['brand_name'],
form.validated['description'],
form.validated['size'])
@@ -334,6 +350,15 @@ class InventoryBatchView(BatchMasterView):
return False
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):
"""
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",
'redirect': self.get_action_url('view', batch),
}
- data = {}
entry = self.request.GET.get('upc', '')
- product = self.find_product(entry)
- data = self.product_info(product)
+ aggregate = self.should_aggregate_products(batch)
- 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:
upc = re.sub(r'\D', '', entry.strip())
if upc:
@@ -358,8 +396,28 @@ class InventoryBatchView(BatchMasterView):
result['upc'] = six.text_type(upc)
result['upc_pretty'] = upc.pretty()
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
+ 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):
upc = re.sub(r'\D', '', entry.strip())
if upc:
@@ -426,41 +484,58 @@ class InventoryBatchView(BatchMasterView):
"""
batch = self.get_instance()
row = None
- upc = self.request.GET.get('upc', '').strip()
- upc = re.sub(r'\D', '', upc)
- if upc:
+ entry = self.request.GET.get('upc', '').strip()
+ entry = re.sub(r'\D', '', entry)
+ if entry:
- if len(upc) <= 14:
- row = self.add_row_for_upc(batch, upc)
+ if len(entry) <= 14:
+ row = self.add_row_for_upc(batch, entry, warn_if_present=True)
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))
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))
self.Session.flush()
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.
"""
- # try to locate general product by UPC; add to batch either way
- provided = GPC(upc, calc_check_digit=False)
- checked = GPC(upc, 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 or self.unknown_product_creates_row:
+ type2 = self.find_type2_product(entry)
+ if type2:
+ product, price = type2
+ else:
+ product = self.find_product(entry)
+ 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()
- if product:
- row.product = product
- row.upc = product.upc
- else:
- row.upc = provided # TODO: why not 'checked' instead? how to choose?
- row.description = "(unknown product)"
+ row.product = product
+ row.upc = product.upc
+ self.handler.capture_current_units(row)
+ if type2 and not aggregate:
+ if price is None:
+ row.units = 1
+ else:
+ 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)"
self.handler.capture_current_units(row)
self.handler.add_row(batch, row)
return row