From 8d16a5f1100d3a6d7ca5756e6807bb74010363d8 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 19 Oct 2021 18:14:50 -0500 Subject: [PATCH] Clean up the product selection UI for new custorder still needs some work but this is much better, more like the customer selection now w/ "multi-faceted" autocomplete --- tailbone/templates/custorders/create.mako | 116 +++++++++------------- tailbone/views/custorders/orders.py | 87 +++++++++------- 2 files changed, 98 insertions(+), 105 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 655caf2b..61c147f5 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -504,41 +504,22 @@ style="padding-left: 5rem;"> - - +

+ Product +

+ + -
- - - - - - - Fetch - - - {{ productUPC }} (click to change) - - View Product @@ -546,18 +527,26 @@
- + +
+ +

{{ productKey }}

+
+ + - $4.20 / EA + {{ productUnitPriceDisplay }} - - 2021-01-01 - + + + + This price is questionable and should be confirmed by someone before order proceeds. @@ -759,6 +748,10 @@ productUUID: null, productDisplay: null, productUPC: null, + productKey: null, + productUnitPriceDisplay: null, + productURL: null, + productImageURL: null, productQuantity: null, defaultUnitChoices: defaultUnitChoices, productUnitChoices: defaultUnitChoices, @@ -1292,13 +1285,15 @@ this.productUUID = null this.productDisplay = null this.productUPC = null + this.productKey = null + this.productUnitPriceDisplay = null this.productQuantity = 1 this.productUnitChoices = this.defaultUnitChoices this.productUOM = this.defaultUOM this.productPriceNeedsConfirmation = false this.showingItemDialog = true this.$nextTick(() => { - this.$refs.productDescriptionAutocomplete.focus() + this.$refs.productAutocomplete.focus() }) }, @@ -1309,6 +1304,10 @@ this.productUUID = row.product_uuid this.productDisplay = row.product_full_description this.productUPC = row.product_upc_pretty || row.product_upc + this.productKey = row.product_key + this.productURL = row.product_url + this.productUnitPriceDisplay = row.unit_price_display + this.productImageURL = row.product_image_url this.productQuantity = row.order_quantity this.productUnitChoices = row.order_uom_choices this.productUOM = row.order_uom @@ -1340,17 +1339,16 @@ }) }, - clearProduct(autofocus) { + clearProduct() { this.productUUID = null this.productDisplay = null this.productUPC = null + this.productKey = null + this.productUnitPriceDisplay = null + this.productURL = null + this.productImageURL = null this.productUnitChoices = this.defaultUnitChoices this.productPriceNeedsConfirmation = false - if (autofocus) { - this.$nextTick(() => { - this.$refs.productUPCInput.focus() - }) - } }, setProductUnitChoices(choices) { @@ -1368,34 +1366,6 @@ } }, - fetchProductByUPC() { - let params = { - action: 'find_product_by_upc', - upc: this.productUPC, - } - this.submitBatchData(params, response => { - if (response.data.error) { - this.$buefy.toast.open({ - message: "Fetch failed: " + response.data.error, - type: 'is-warning', - duration: 2000, // 2 seconds - }) - } else { - this.productUUID = response.data.uuid - this.productUPC = response.data.upc_pretty - this.productDisplay = response.data.full_description - this.setProductUnitChoices(response.data.uom_choices) - this.productPriceNeedsConfirmation = false - } - }) - }, - - productUPCKeyDown(event) { - if (event.which == 13) { // Enter - this.fetchProductByUPC() - } - }, - productChanged(uuid) { if (uuid) { this.productUUID = uuid @@ -1405,7 +1375,11 @@ } this.submitBatchData(params, response => { this.productUPC = response.data.upc_pretty + this.productKey = response.data.key this.productDisplay = response.data.full_description + this.productUnitPriceDisplay = response.data.unit_price_display + this.productURL = response.data.url + this.productImageURL = response.data.image_url this.setProductUnitChoices(response.data.uom_choices) this.productPriceNeedsConfirmation = false }) diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index 0153126a..1d564f35 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -31,7 +31,6 @@ import decimal import six from sqlalchemy import orm -from rattail import pod from rattail.db import model from rattail.util import pretty_quantity from rattail.batch import get_batch_handler @@ -259,7 +258,6 @@ class CustomerOrderView(MasterView): 'update_pending_customer', 'get_customer_info', # 'set_customer_data', - 'find_product_by_upc', 'get_product_info', 'add_item', 'update_item', @@ -273,12 +271,6 @@ class CustomerOrderView(MasterView): items = [self.normalize_row(row) for row in batch.active_rows()] - if self.handler.has_custom_product_autocomplete: - route_prefix = self.get_route_prefix() - product_autocomplete = '{}.product_autocomplete'.format(route_prefix) - else: - product_autocomplete = 'products.autocomplete' - context = self.get_context_contact(batch) context.update({ @@ -288,7 +280,6 @@ class CustomerOrderView(MasterView): 'allow_contact_info_choice': self.handler.allow_contact_info_choice(), 'restrict_contact_info': self.handler.should_restrict_contact_info(), 'order_items': items, - 'product_autocomplete_url': self.request.route_url(product_autocomplete), }) return self.render_to_response(template, context) @@ -535,31 +526,18 @@ class CustomerOrderView(MasterView): """ Custom product autocomplete logic, which invokes the handler. """ - self.handler = self.get_batch_handler() term = self.request.GET['term'] - return self.handler.custom_product_autocomplete(self.Session(), term, - user=self.request.user) - def find_product_by_upc(self, batch, data): - upc = data.get('upc') - if not upc: - return {'error': "Must specify a product UPC"} + # if handler defines custom autocomplete, use that + handler = self.get_batch_handler() + if handler.has_custom_product_autocomplete: + return handler.custom_product_autocomplete(self.Session(), term, + user=self.request.user) - product = self.handler.locate_product_for_entry( - self.Session(), upc, product_key='upc', - # nb. let handler know "why" we're doing this, so that it - # can "modify" the result accordingly, i.e. return the - # appropriate item when a "different" scancode is entered - # by the user (e.g. PLU, and/or units vs. packs) - variation='new_custorder') - if not product: - return {'error': "Product not found"} - - reason = self.handler.why_not_add_product(product, batch) - if reason: - return {'error': reason} - - return self.info_for_product(batch, data, product) + # otherwise we use 'products.neworder' autocomplete + app = self.get_rattail_app() + autocomplete = app.get_autocompleter('products.neworder') + return autocomplete.autocomplete(self.Session(), term) def get_product_info(self, batch, data): uuid = data.get('uuid') @@ -608,15 +586,27 @@ class CustomerOrderView(MasterView): return choices def info_for_product(self, batch, data, product): - return { + app = self.get_rattail_app() + products = app.get_products_handler() + data = { 'uuid': product.uuid, 'upc': six.text_type(product.upc), 'upc_pretty': product.upc.pretty(), + 'unit_price_display': self.get_unit_price_display(product), 'full_description': product.full_description, - 'image_url': pod.get_image_url(self.rattail_config, product.upc), + 'url': self.request.route_url('products.view', uuid=product.uuid), + 'image_url': products.get_image_url(product), 'uom_choices': self.uom_choices_for_product(product), } + key = self.rattail_config.product_key() + if key == 'upc': + data['key'] = data['upc_pretty'] + else: + data['key'] = getattr(product, key, data['upc_pretty']) + + return data + def normalize_batch(self, batch): return { 'uuid': batch.uuid, @@ -626,7 +616,24 @@ class CustomerOrderView(MasterView): 'status_text': batch.status_text, } + def get_unit_price_display(self, obj): + """ + Returns a display string for the given object's unit price. + The object can be either a ``Product`` instance, or a batch + row. + """ + app = self.get_rattail_app() + model = self.model + if isinstance(obj, model.Product): + products = app.get_products_handler() + return products.render_price(obj.regular_price) + else: # row + return app.render_currency(obj.unit_price) + def normalize_row(self, row): + app = self.get_rattail_app() + products = app.get_products_handler() + product = row.product department = product.department if product else None cost = product.cost if product else None @@ -654,7 +661,7 @@ class CustomerOrderView(MasterView): 'vendor_display': cost.vendor.name if cost else None, 'unit_price': six.text_type(row.unit_price) if row.unit_price is not None else None, - 'unit_price_display': "${:0.2f}".format(row.unit_price) if row.unit_price is not None else None, + 'unit_price_display': self.get_unit_price_display(row), 'total_price': six.text_type(row.total_price) if row.total_price is not None else None, 'total_price_display': "${:0.2f}".format(row.total_price) if row.total_price is not None else None, 'price_needs_confirmation': row.price_needs_confirmation, @@ -663,6 +670,18 @@ class CustomerOrderView(MasterView): 'status_text': row.status_text, } + key = self.rattail_config.product_key() + if key == 'upc': + data['product_key'] = data['product_upc_pretty'] + else: + data['product_key'] = getattr(product, key, data['product_upc_pretty']) + + if row.product: + data.update({ + 'product_url': self.request.route_url('products.view', uuid=row.product.uuid), + 'product_image_url': products.get_image_url(row.product), + }) + unit_uom = self.enum.UNIT_OF_MEASURE_POUND if data['product_weighed'] else self.enum.UNIT_OF_MEASURE_EACH if row.order_uom == self.enum.UNIT_OF_MEASURE_CASE: if row.case_quantity is None: