From db645fb393a66a3a78325d55a8b82b80d2df8ee4 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 1 Jun 2018 12:49:01 -0500 Subject: [PATCH] Add support for variance inventory batches, aggregation by product kind of a rushed job but hopefully this is all good... --- .../batch/inventory/desktop_form.mako | 18 ++- tailbone/views/inventory.py | 147 +++++++++++++----- 2 files changed, 127 insertions(+), 38 deletions(-) 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