From 8a378317c0950d53727f05e0daf96926c60238ce Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 3 Nov 2021 18:15:13 -0500 Subject: [PATCH 0001/1212] Try to prevent caching for any /index (grid) page if this works, maybe also should do it for /view since that can have a rows grid? --- tailbone/views/master.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index ce7fcca7..46b652e8 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -3941,7 +3941,13 @@ class MasterView(View): "List / search {}".format(model_title_plural)) config.add_route(route_prefix, '{}/'.format(url_prefix)) config.add_view(cls, attr='index', route_name=route_prefix, - permission='{}.list'.format(permission_prefix)) + permission='{}.list'.format(permission_prefix), + # hopefully, instruct browser to never cache this page. + # on windows/chrome we are seeing some caching when e.g. + # user applies some filters, then views a record, then + # clicks back button, filters no longer are applied + # cf. https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/viewconfig.html#non-predicate-arguments + http_cache=0) # download results # this is the "new" more flexible approach, but we only want to From b0fa559760a577edda4569106f60587573b4770a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 3 Nov 2021 18:30:16 -0500 Subject: [PATCH 0002/1212] Fix product view page when user cannot view version history --- tailbone/templates/products/view.mako | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tailbone/templates/products/view.mako b/tailbone/templates/products/view.mako index 08aa348a..0d0e4e5f 100644 --- a/tailbone/templates/products/view.mako +++ b/tailbone/templates/products/view.mako @@ -544,8 +544,12 @@ <%def name="modify_this_page_vars()"> ${parent.modify_this_page_vars()} - % if request.rattail_config.versioning_enabled() and master.has_perm('versions'): - - % endif + % endif + From 4d33e3dcbe981d73dcc7b562764ab5d5bf8ec360 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 3 Nov 2021 19:19:20 -0500 Subject: [PATCH 0003/1212] Move some custorder logic to handler; allow force-swap of product selection --- .../static/js/tailbone.buefy.autocomplete.js | 15 +++-- tailbone/templates/custorders/create.mako | 12 +++- tailbone/views/custorders/orders.py | 57 ++----------------- 3 files changed, 24 insertions(+), 60 deletions(-) diff --git a/tailbone/static/js/tailbone.buefy.autocomplete.js b/tailbone/static/js/tailbone.buefy.autocomplete.js index 7969f35a..53c41b40 100644 --- a/tailbone/static/js/tailbone.buefy.autocomplete.js +++ b/tailbone/static/js/tailbone.buefy.autocomplete.js @@ -32,10 +32,17 @@ const TailboneAutocomplete = { // allows for the "label" to display correctly as well initialLabel: String, - // TODO: i am not sure this is needed? but current logic does - // handle it specially, so am leaving for now. if this prop - // is set by the caller, then the `assignedLabel` will *always* - // be shown for the button (when "selection" has been made) + // while the `initialLabel` above is useful for setting the + // *initial* label (of course), it cannot be used to + // arbitrarily update the label during the component's life. + // if you do need to *update* the label after initial page + // load, then you should set `assignedLabel` instead. one + // place this happens is in /custorders/create page, where + // product autocomplete shows some results, and user clicks + // one, but then handler logic can forcibly "swap" the + // selection, causing *different* product data to come back + // from the server, and autocomplete label should be updated + // to match. this feels a bit awkward still but does work.. assignedLabel: String, // simple placeholder text for the input box diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 61c147f5..c6bcbd64 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -511,7 +511,7 @@ @@ -1368,12 +1368,18 @@ productChanged(uuid) { if (uuid) { - this.productUUID = uuid let params = { action: 'get_product_info', - uuid: this.productUUID, + uuid: uuid, } + // nb. it is possible for the handler to "swap" + // the product selection, i.e. user chooses a "per + // LB" item but the handler only allows selling by + // the "case" item. so we do not assume the uuid + // received above is the correct one, but just use + // whatever came back from handler this.submitBatchData(params, response => { + this.productUUID = response.data.uuid this.productUPC = response.data.upc_pretty this.productKey = response.data.key this.productDisplay = response.data.full_description diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index 1d564f35..ce74bac2 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -551,61 +551,12 @@ class CustomerOrderView(MasterView): return self.info_for_product(batch, data, product) def uom_choices_for_product(self, product): - choices = [] - - # Each - if not product or not product.weighed: - unit_name = self.enum.UNIT_OF_MEASURE[self.enum.UNIT_OF_MEASURE_EACH] - choices.append({'key': self.enum.UNIT_OF_MEASURE_EACH, - 'value': unit_name}) - - # Pound - if not product or product.weighed: - unit_name = self.enum.UNIT_OF_MEASURE[self.enum.UNIT_OF_MEASURE_POUND] - choices.append({ - 'key': self.enum.UNIT_OF_MEASURE_POUND, - 'value': unit_name, - }) - - # Case - case_text = None - case_size = self.handler.get_case_size_for_product(product) - if case_size is None: - case_text = "{} (× ?? {})".format( - self.enum.UNIT_OF_MEASURE[self.enum.UNIT_OF_MEASURE_CASE], - unit_name) - elif case_size > 1: - case_text = "{} (× {} {})".format( - self.enum.UNIT_OF_MEASURE[self.enum.UNIT_OF_MEASURE_CASE], - pretty_quantity(case_size), - unit_name) - if case_text: - choices.append({'key': self.enum.UNIT_OF_MEASURE_CASE, - 'value': case_text}) - - return choices + return self.handler.uom_choices_for_product(product) def info_for_product(self, batch, data, product): - 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, - '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 + info = self.handler.get_product_info(batch, product) + info['url'] = self.request.route_url('products.view', uuid=info['uuid']) + return info def normalize_batch(self, batch): return { From 1bdb845032edae646bf3f3be5df462f562f5aa63 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 3 Nov 2021 20:20:22 -0500 Subject: [PATCH 0004/1212] Honor the "product price may be questionable" flag for new custorder i.e. don't expose the per-item flag unless *that* flag is set --- tailbone/templates/custorders/create.mako | 27 ++++++++++++++++++++++- tailbone/views/custorders/orders.py | 22 ++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index c6bcbd64..7d368bdf 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -545,12 +545,14 @@ + % if product_price_may_be_questionable: This price is questionable and should be confirmed by someone before order proceeds. + % endif @@ -629,7 +631,11 @@ - + {{ props.row.total_price_display }} @@ -758,7 +764,10 @@ defaultUOM: defaultUOM, productUOM: defaultUOM, productCaseSize: null, + + % if product_price_may_be_questionable: productPriceNeedsConfirmation: false, + % endif ## TODO: should find a better way to handle CSRF token csrftoken: ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n}, @@ -1290,7 +1299,11 @@ this.productQuantity = 1 this.productUnitChoices = this.defaultUnitChoices this.productUOM = this.defaultUOM + + % if product_price_may_be_questionable: this.productPriceNeedsConfirmation = false + % endif + this.showingItemDialog = true this.$nextTick(() => { this.$refs.productAutocomplete.focus() @@ -1311,7 +1324,10 @@ this.productQuantity = row.order_quantity this.productUnitChoices = row.order_uom_choices this.productUOM = row.order_uom + + % if product_price_may_be_questionable: this.productPriceNeedsConfirmation = row.price_needs_confirmation + % endif this.showingItemDialog = true }, @@ -1348,7 +1364,10 @@ this.productURL = null this.productImageURL = null this.productUnitChoices = this.defaultUnitChoices + + % if product_price_may_be_questionable: this.productPriceNeedsConfirmation = false + % endif }, setProductUnitChoices(choices) { @@ -1387,7 +1406,10 @@ this.productURL = response.data.url this.productImageURL = response.data.image_url this.setProductUnitChoices(response.data.uom_choices) + + % if product_price_may_be_questionable: this.productPriceNeedsConfirmation = false + % endif }) } else { this.clearProduct() @@ -1401,7 +1423,10 @@ product_uuid: this.productUUID, order_quantity: this.productQuantity, order_uom: this.productUOM, + + % if product_price_may_be_questionable: price_needs_confirmation: this.productPriceNeedsConfirmation, + % endif } if (this.editingItem) { diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index ce74bac2..b3d44183 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -188,7 +188,11 @@ class CustomerOrderView(MasterView): g.set_type('order_quantity', 'quantity') g.set_type('cases_ordered', 'quantity') g.set_type('units_ordered', 'quantity') - g.set_renderer('total_price', self.render_price_with_confirmation) + + if self.handler.product_price_may_be_questionable(): + g.set_renderer('total_price', self.render_price_with_confirmation) + else: + g.set_type('total_price', 'currency') g.set_enum('order_uom', self.enum.UNIT_OF_MEASURE) g.set_renderer('status_code', self.render_row_status_code) @@ -277,6 +281,7 @@ class CustomerOrderView(MasterView): 'batch': batch, 'normalized_batch': self.normalize_batch(batch), 'new_order_requires_customer': self.handler.new_order_requires_customer(), + 'product_price_may_be_questionable': self.handler.product_price_may_be_questionable(), 'allow_contact_info_choice': self.handler.allow_contact_info_choice(), 'restrict_contact_info': self.handler.should_restrict_contact_info(), 'order_items': items, @@ -615,12 +620,14 @@ class CustomerOrderView(MasterView): '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, 'status_code': row.status_code, 'status_text': row.status_text, } + if self.handler.product_price_may_be_questionable(): + data['price_needs_confirmation'] = row.price_needs_confirmation + key = self.rattail_config.product_key() if key == 'upc': data['product_key'] = data['product_upc_pretty'] @@ -669,10 +676,14 @@ class CustomerOrderView(MasterView): if not product: return {'error': "Product not found"} + kwargs = {} + if self.handler.product_price_may_be_questionable(): + kwargs['price_needs_confirmation'] = data.get('price_needs_confirmation') + row = self.handler.add_product(batch, product, decimal.Decimal(data.get('order_quantity') or '0'), data.get('order_uom'), - price_needs_confirmation=data.get('price_needs_confirmation')) + **kwargs) self.Session.flush() else: # product is not known @@ -707,7 +718,10 @@ class CustomerOrderView(MasterView): row.product = product row.order_quantity = decimal.Decimal(data.get('order_quantity') or '0') row.order_uom = data.get('order_uom') - row.price_needs_confirmation = data.get('price_needs_confirmation') + + if self.handler.product_price_may_be_questionable(): + row.price_needs_confirmation = data.get('price_needs_confirmation') + self.handler.refresh_row(row) self.Session.flush() self.Session.refresh(row) From 0758ca09e65bfc3e90ce26a713a4d61919e9323f Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 3 Nov 2021 20:54:46 -0500 Subject: [PATCH 0005/1212] Show unit price in line items grid for new custorder maybe should change this to show "base price" (unit *or* case depending on the row uom) ? --- tailbone/templates/custorders/create.mako | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 7d368bdf..3e409a34 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -630,6 +630,16 @@ + + + {{ props.row.unit_price_display }} + + + Date: Thu, 4 Nov 2021 21:20:42 -0500 Subject: [PATCH 0006/1212] Avoid exposing batch params when creating a batch not sure how this never came up until now..? --- tailbone/views/batch/core.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py index 07b7ff68..821628aa 100644 --- a/tailbone/views/batch/core.py +++ b/tailbone/views/batch/core.py @@ -341,6 +341,10 @@ class BatchMasterView(MasterView): f.set_renderer('id', self.render_id_str) f.set_label('id', "Batch ID") + # params + if self.creating: + f.remove('params') + # created f.set_readonly('created') f.set_readonly('created_by') From eb76d868ca9754b30770b1ddd3f122a7c58609b6 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 4 Nov 2021 21:25:32 -0500 Subject: [PATCH 0007/1212] Update changelog --- CHANGES.rst | 16 ++++++++++++++++ tailbone/_version.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 1c89676b..8fef5dea 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,22 @@ CHANGELOG ========= +0.8.167 (2021-11-04) +-------------------- + +* Try to prevent caching for any /index (grid) page. + +* Fix product view page when user cannot view version history. + +* Move some custorder logic to handler; allow force-swap of product selection. + +* Honor the "product price may be questionable" flag for new custorder. + +* Show unit price in line items grid for new custorder. + +* Avoid exposing batch params when creating a batch. + + 0.8.166 (2021-11-03) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 6de0b83d..44127df5 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.166' +__version__ = '0.8.167' From 2be1d121161d36305875f615c5d42cd3ed17baff Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 5 Nov 2021 15:11:07 -0500 Subject: [PATCH 0008/1212] Make separate method for writing results XLSX file so subclass can customize --- tailbone/views/master.py | 45 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 46b652e8..1ab87bb6 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -2722,6 +2722,29 @@ class MasterView(View): def results_xlsx_session(self): return self.make_isolated_session() + def results_write_xlsx(self, path, fields, results, session, progress=None): + writer = ExcelWriter(path, fields, sheet_title=self.get_model_title_plural()) + writer.write_header() + + rows = [] + def write(obj, i): + data = self.get_xlsx_row(obj, fields) + row = [data[field] for field in fields] + rows.append(row) + + self.progress_loop(write, results, progress, + message="Collecting data for Excel") + + def finalize(x, i): + writer.write_rows(rows) + writer.auto_freeze() + writer.auto_filter() + writer.auto_resize() + writer.save() + + self.progress_loop(finalize, [1], progress, + message="Writing Excel file to disk") + def results_xlsx_thread(self, results, user_uuid, progress): """ Thread target, responsible for actually generating the Excel file which @@ -2743,27 +2766,9 @@ class MasterView(View): results = results.with_session(session).all() fields = self.get_xlsx_fields() - writer = ExcelWriter(path, fields, sheet_title=self.get_model_title_plural()) - writer.write_header() - rows = [] - def write(obj, i): - data = self.get_xlsx_row(obj, fields) - row = [data[field] for field in fields] - rows.append(row) - - self.progress_loop(write, results, progress, - message="Collecting data for Excel") - - def finalize(x, i): - writer.write_rows(rows) - writer.auto_freeze() - writer.auto_filter() - writer.auto_resize() - writer.save() - - self.progress_loop(finalize, [1], progress, - message="Writing Excel file to disk") + # write output file + self.results_write_xlsx(path, fields, results, session, progress=progress) except Exception as error: msg = "generating XLSX file for download failed!" From df8778f85d33f9f1176e97c1d11842cf14757b64 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 5 Nov 2021 15:11:30 -0500 Subject: [PATCH 0009/1212] Add `render_brand()` method for MasterView --- tailbone/views/master.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tailbone/views/master.py b/tailbone/views/master.py index 1ab87bb6..1eb7686a 100644 --- a/tailbone/views/master.py +++ b/tailbone/views/master.py @@ -791,6 +791,14 @@ class MasterView(View): url = self.request.route_url('subdepartments.view', uuid=subdepartment.uuid) return tags.link_to(text, url) + def render_brand(self, obj, field): + brand = getattr(obj, field) + if not brand: + return + text = brand.name + url = self.request.route_url('brands.view', uuid=brand.uuid) + return tags.link_to(text, url) + def render_category(self, obj, field): category = getattr(obj, field) if not category: From 5ff57ae7d223c3061409e76950982e97a74b6e77 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 5 Nov 2021 18:40:46 -0500 Subject: [PATCH 0010/1212] Add link to download generic template for vendor catalog batch also let config restrict which parsers are "supported" and auto-choose parser if there is only one --- .../templates/batch/vendorcatalog/index.mako | 3 +++ tailbone/views/batch/vendorcatalog.py | 22 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tailbone/templates/batch/vendorcatalog/index.mako b/tailbone/templates/batch/vendorcatalog/index.mako index fa6e4a5a..1fac1170 100644 --- a/tailbone/templates/batch/vendorcatalog/index.mako +++ b/tailbone/templates/batch/vendorcatalog/index.mako @@ -3,6 +3,9 @@ <%def name="context_menu_items()"> ${parent.context_menu_items()} + % if generic_template_url and master.has_perm('create'): +
  • ${h.link_to("Download Generic Template", generic_template_url)}
  • + % endif % if h.route_exists(request, 'vendors') and request.has_perm('vendors.list'):
  • ${h.link_to("View Vendors", url('vendors'))}
  • % endif diff --git a/tailbone/views/batch/vendorcatalog.py b/tailbone/views/batch/vendorcatalog.py index adcf5dff..7a1a4153 100644 --- a/tailbone/views/batch/vendorcatalog.py +++ b/tailbone/views/batch/vendorcatalog.py @@ -135,7 +135,13 @@ class VendorCatalogView(FileBatchMasterView): def get_parsers(self): if not hasattr(self, 'parsers'): - self.parsers = sorted(iter_catalog_parsers(), key=lambda p: p.display) + parsers = sorted(iter_catalog_parsers(), key=lambda p: p.display) + supported = self.rattail_config.getlist( + 'tailbone', 'batch.vendorcatalog.supported_parsers') + if supported: + parsers = [parser for parser in parsers + if parser.key in supported] + self.parsers = parsers return self.parsers def configure_grid(self, g): @@ -176,8 +182,13 @@ class VendorCatalogView(FileBatchMasterView): if self.creating: if 'parser_key' not in f: f.insert_after('filename', 'parser_key') - values = [(p.key, p.display) for p in self.get_parsers()] - values.insert(0, ('', "(please choose)")) + parsers = self.get_parsers() + values = [(p.key, p.display) for p in parsers] + if len(values) == 1: + f.set_default('parser_key', parsers[0].key) + use_buefy = self.get_use_buefy() + if not use_buefy: + values.insert(0, ('', "(please choose)")) f.set_widget('parser_key', dfwidget.SelectWidget(values=values)) f.set_label('parser_key', "File Type") @@ -241,6 +252,11 @@ class VendorCatalogView(FileBatchMasterView): f.set_type('upc', 'gpc') f.set_type('discount_percent', 'percent') + def template_kwargs_index(self, **kwargs): + url = self.rattail_config.get('tailbone', 'batch.vendorcatalog.generic_template_url') + kwargs['generic_template_url'] = url + return kwargs + def template_kwargs_create(self, **kwargs): parsers = self.get_parsers() for parser in parsers: From 28e908524962d942a5fa36e1e244e75ec237aaf2 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Fri, 5 Nov 2021 18:45:45 -0500 Subject: [PATCH 0011/1212] Update changelog --- CHANGES.rst | 10 ++++++++++ tailbone/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 8fef5dea..5965aed3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,16 @@ CHANGELOG ========= +0.8.168 (2021-11-05) +-------------------- + +* Make separate method for writing results XLSX file. + +* Add ``render_brand()`` method for MasterView. + +* Add link to download generic template for vendor catalog batch. + + 0.8.167 (2021-11-04) -------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index 44127df5..35b813c7 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.8.167' +__version__ = '0.8.168' From 7a5ba0503ae64a4c0cde77b82e945935a6e15a07 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 6 Nov 2021 17:36:19 -0500 Subject: [PATCH 0012/1212] Use products handler to get image URL --- tailbone/views/products.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/tailbone/views/products.py b/tailbone/views/products.py index cc6a47ec..3b6f45f0 100644 --- a/tailbone/views/products.py +++ b/tailbone/views/products.py @@ -1067,23 +1067,7 @@ class ProductView(MasterView): product = kwargs['instance'] use_buefy = self.get_use_buefy() - # TODO: pretty sure this is no longer needed? guess we'll find out - # kwargs['image'] = False - - # maybe provide image URL for product; we prefer image from our DB if - # present, but otherwise a "POD" image URL can be attempted. - if product.image: - kwargs['image_url'] = self.request.route_url('products.image', uuid=product.uuid) - - elif product.upc: - if self.rattail_config.getbool('tailbone', 'products.show_pod_image', default=False): - # here we try to give a URL to a so-called "POD" image for the product - kwargs['image_url'] = pod.get_image_url(self.rattail_config, product.upc) - kwargs['image_path'] = pod.get_image_path(self.rattail_config, product.upc) - - # maybe use "image not found" placeholder image - if not kwargs.get('image_url'): - kwargs['image_url'] = self.request.static_url('tailbone:static/img/product.png') + kwargs['image_url'] = self.handler.get_image_url(product) # add price history, if user has access if self.rattail_config.versioning_enabled() and self.has_perm('versions'): From 43bbc2a29ea0e2875e361064fad5a9df31487d73 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 6 Nov 2021 17:37:05 -0500 Subject: [PATCH 0013/1212] Show some more product attributes in custorder item selection popup --- tailbone/templates/custorders/create.mako | 76 ++++++++++++++++++++++- tailbone/views/custorders/orders.py | 5 ++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 3e409a34..f8b7b9cb 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -531,18 +531,35 @@
    -

    {{ productKey }}

    + ##

    {{ productKey }}

    + + {{ productKey }} + + + + {{ productSize }} + + - + {{ productUnitPriceDisplay }} + + + {{ productCaseQuantity }} + + % if product_price_may_be_questionable: @@ -568,6 +585,12 @@ + + + {{ productDisplay }} + + + @@ -583,6 +606,39 @@ + + + + + {{ productSize }} + + + + + {{ productUnitPriceDisplay }} + + + + + {{ productCaseQuantity }} + + + + + {{ productCasePriceDisplay }} + + + + + @@ -765,7 +821,11 @@ productDisplay: null, productUPC: null, productKey: null, + productKeyLabel: ${json.dumps(product_key_label)|n}, + productSize: null, + productCaseQuantity: null, productUnitPriceDisplay: null, + productCasePriceDisplay: null, productURL: null, productImageURL: null, productQuantity: null, @@ -1305,7 +1365,10 @@ this.productDisplay = null this.productUPC = null this.productKey = null + this.productSize = null + this.productCaseQuantity = null this.productUnitPriceDisplay = null + this.productCasePriceDisplay = null this.productQuantity = 1 this.productUnitChoices = this.defaultUnitChoices this.productUOM = this.defaultUOM @@ -1328,8 +1391,11 @@ this.productDisplay = row.product_full_description this.productUPC = row.product_upc_pretty || row.product_upc this.productKey = row.product_key + this.productSize = row.product_size + this.productCaseQuantity = row.case_quantity this.productURL = row.product_url this.productUnitPriceDisplay = row.unit_price_display + this.productCasePriceDisplay = row.case_price_display this.productImageURL = row.product_image_url this.productQuantity = row.order_quantity this.productUnitChoices = row.order_uom_choices @@ -1370,7 +1436,10 @@ this.productDisplay = null this.productUPC = null this.productKey = null + this.productSize = null + this.productCaseQuantity = null this.productUnitPriceDisplay = null + this.productCasePriceDisplay = null this.productURL = null this.productImageURL = null this.productUnitChoices = this.defaultUnitChoices @@ -1412,7 +1481,10 @@ this.productUPC = response.data.upc_pretty this.productKey = response.data.key this.productDisplay = response.data.full_description + this.productSize = response.data.size + this.productCaseQuantity = response.data.case_quantity this.productUnitPriceDisplay = response.data.unit_price_display + this.productCasePriceDisplay = response.data.case_price_display this.productURL = response.data.url this.productImageURL = response.data.image_url this.setProductUnitChoices(response.data.uom_choices) diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index b3d44183..9065c330 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -285,6 +285,7 @@ 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_key_label': self.rattail_config.product_key_title(), }) return self.render_to_response(template, context) @@ -625,6 +626,10 @@ class CustomerOrderView(MasterView): 'status_text': row.status_text, } + case_price = self.handler.get_case_price_for_row(row) + data['case_price'] = six.text_type(case_price) if case_price is not None else None + data['case_price_display'] = app.render_currency(case_price) + if self.handler.product_price_may_be_questionable(): data['price_needs_confirmation'] = row.price_needs_confirmation From ddb05afe6b5e0e84fda69a645733ca23a87ce139 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 6 Nov 2021 17:56:35 -0500 Subject: [PATCH 0014/1212] Auto-select Quantity tab when editing item for new custorder also be a little smarter on error when user selects an item --- tailbone/templates/custorders/create.mako | 21 ++++++++++++++++++--- tailbone/views/custorders/orders.py | 10 +++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index f8b7b9cb..520ed0f4 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -489,6 +489,7 @@
    @@ -816,6 +817,7 @@ items: ${json.dumps(order_items)|n}, editingItem: null, showingItemDialog: false, + itemDialogTabIndex: 0, productIsKnown: true, productUUID: null, productDisplay: null, @@ -1105,7 +1107,7 @@ }) }, - submitBatchData(params, callback) { + submitBatchData(params, success, failure) { let url = ${json.dumps(request.current_route_url())|n} let headers = { @@ -1115,8 +1117,17 @@ ## TODO: should find a better way to handle CSRF token this.$http.post(url, params, {headers: headers}).then((response) => { - if (callback) { - callback(response) + if (response.data.error) { + this.$buefy.toast.open({ + message: response.data.error, + type: 'is-danger', + duration: 2000, // 2 seconds + }) + if (failure) { + failure(response) + } + } else if (success) { + success(response) } }, response => { this.$buefy.toast.open({ @@ -1377,6 +1388,7 @@ this.productPriceNeedsConfirmation = false % endif + this.itemDialogTabIndex = 0 this.showingItemDialog = true this.$nextTick(() => { this.$refs.productAutocomplete.focus() @@ -1405,6 +1417,7 @@ this.productPriceNeedsConfirmation = row.price_needs_confirmation % endif + this.itemDialogTabIndex = 1 this.showingItemDialog = true }, @@ -1492,6 +1505,8 @@ % if product_price_may_be_questionable: this.productPriceNeedsConfirmation = false % endif + }, response => { + this.clearProduct() }) } else { this.clearProduct() diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index 9065c330..1cc36aca 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -560,9 +560,13 @@ class CustomerOrderView(MasterView): return self.handler.uom_choices_for_product(product) def info_for_product(self, batch, data, product): - info = self.handler.get_product_info(batch, product) - info['url'] = self.request.route_url('products.view', uuid=info['uuid']) - return info + try: + info = self.handler.get_product_info(batch, product) + except Exception as error: + return {'error': six.text_type(error)} + else: + info['url'] = self.request.route_url('products.view', uuid=info['uuid']) + return info def normalize_batch(self, batch): return { From 5d875bc731d855b05f9d4048f79357ab811ff0d3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Sat, 6 Nov 2021 20:00:54 -0500 Subject: [PATCH 0015/1212] Let user "add past product" when making new custorder --- tailbone/templates/custorders/create.mako | 188 ++++++++++++++++++++-- tailbone/views/custorders/orders.py | 16 ++ 2 files changed, 188 insertions(+), 16 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 520ed0f4..3daff955 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -483,7 +483,13 @@ @click="showAddItemDialog()"> Add Item + + Add Past Item +
    +
    @@ -586,28 +592,18 @@ +
    + + ##

    {{ productKey }}

    +
    + {{ productDisplay }} - - - - - - - - - - - - @@ -640,6 +636,22 @@ + + + + + + + + + + + +
    @@ -659,6 +671,93 @@
    + +
    +
    + + + + + + +
    + + Cancel + + + Add Selected Item + +
    + +
    +
    +
    +