From f708cb0b253b1b6fe4f23889bfba2cd2bba99be3 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 24 Oct 2023 17:44:02 -0500 Subject: [PATCH 001/398] Fix bug when editing vendor --- tailbone/views/vendors/core.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tailbone/views/vendors/core.py b/tailbone/views/vendors/core.py index 743e1632..8b9361b7 100644 --- a/tailbone/views/vendors/core.py +++ b/tailbone/views/vendors/core.py @@ -78,7 +78,7 @@ class VendorView(MasterView): ] def configure_grid(self, g): - super(VendorView, self).configure_grid(g) + super().configure_grid(g) g.filters['name'].default_active = True g.filters['name'].default_verb = 'contains' @@ -124,8 +124,9 @@ class VendorView(MasterView): def objectify(self, form, data=None): if data is None: data = form.validated - vendor = super(VendorView, self).objectify(form, data) + vendor = super().objectify(form, data) vendor = self.objectify_contact(vendor, data) + app = self.get_rattail_app() if 'orders_email' in data: address = data['orders_email'] @@ -169,7 +170,7 @@ class VendorView(MasterView): self.Session.delete(cost) def get_version_child_classes(self): - return super(VendorView, self).get_version_child_classes() + [ + return super().get_version_child_classes() + [ (model.VendorPhoneNumber, 'parent_uuid'), (model.VendorEmailAddress, 'parent_uuid'), (model.VendorContact, 'vendor_uuid'), @@ -186,14 +187,14 @@ class VendorView(MasterView): ] def configure_get_context(self, **kwargs): - context = super(VendorView, self).configure_get_context(**kwargs) + context = super().configure_get_context(**kwargs) context['supported_vendor_settings'] = self.configure_get_supported_vendor_settings() return context def configure_gather_settings(self, data, **kwargs): - settings = super(VendorView, self).configure_gather_settings( + settings = super().configure_gather_settings( data, **kwargs) supported_vendor_settings = self.configure_get_supported_vendor_settings() @@ -205,7 +206,7 @@ class VendorView(MasterView): return settings def configure_remove_settings(self, **kwargs): - super(VendorView, self).configure_remove_settings(**kwargs) + super().configure_remove_settings(**kwargs) app = self.get_rattail_app() names = [] From e308108bf761ab446c552255e5e175a640ae4f42 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 24 Oct 2023 17:48:08 -0500 Subject: [PATCH 002/398] Show user warning if "add item to custorder" fails specifically, if user enters alpha chars for cost/price fields --- tailbone/templates/custorders/create.mako | 2 ++ tailbone/views/custorders/orders.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 663c4300..7d3b367f 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -2124,6 +2124,8 @@ this.itemDialogSaving = false this.showingItemDialog = false + }, response => { + this.itemDialogSaving = false }) }, }, diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index f88886bb..60949e8f 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -864,7 +864,10 @@ class CustomerOrderView(MasterView): for field in ('unit_cost', 'regular_price_amount', 'case_size'): if field in pending_info: - pending_info[field] = decimal.Decimal(pending_info[field]) + try: + pending_info[field] = decimal.Decimal(pending_info[field]) + except decimal.InvalidOperation: + return {'error': f"Invalid entry for field: {field}"} pending_info['user'] = self.request.user From 4247804707b69e0fe6f2291e027307060c42696e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 24 Oct 2023 19:17:36 -0500 Subject: [PATCH 003/398] Allow pending product fields to be required, for new custorder --- tailbone/templates/custorders/configure.mako | 44 +++++-- tailbone/templates/custorders/create.mako | 128 ++++++++++++------- tailbone/views/custorders/orders.py | 52 +++++++- 3 files changed, 167 insertions(+), 57 deletions(-) diff --git a/tailbone/templates/custorders/configure.mako b/tailbone/templates/custorders/configure.mako index ee1f06c5..3f7041d3 100644 --- a/tailbone/templates/custorders/configure.mako +++ b/tailbone/templates/custorders/configure.mako @@ -79,15 +79,6 @@ - - - Allow creating orders for "unknown" products - - - + +

Unknown Products

+
+ + + + Allow creating orders for "unknown" products + + + +
+ +

+ Require these fields for new product: +

+ +
+ % for field in pending_product_fields: + + + ${field} + + + % endfor +
+ +
+ +
diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 7d3b367f..dbcd81b3 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -28,7 +28,7 @@ :disabled="submittingOrder" icon-pack="fas" icon-left="fas fa-upload"> - {{ submitOrderButtonText }} + {{ submittingOrder ? "Working, please wait..." : "Submit this Order" }} - + + % if 'description' in pending_product_required_fields: + :type="pendingProduct.description ? null : 'is-danger'" + % endif + > - + @@ -664,12 +675,20 @@ - + - + - - + + + + + + + + + + + + + + + + @@ -690,27 +736,24 @@ - - - - - - - - - - - + - - + + @@ -854,7 +897,7 @@ :disabled="itemDialogSaveDisabled" icon-pack="fas" icon-left="save"> - {{ itemDialogSaveButtonText }} + {{ itemDialogSaving ? "Working, please wait..." : (this.editingItem ? "Update Item" : "Add Item") }} @@ -1197,6 +1240,7 @@ % endif pendingProduct: {}, + pendingProductRequiredFields: ${json.dumps(pending_product_required_fields)|n}, departmentOptions: ${json.dumps(department_options)|n}, submittingOrder: false, @@ -1385,37 +1429,30 @@ % endif itemDialogSaveDisabled() { + if (this.itemDialogSaving) { return true } + if (this.productIsKnown) { if (!this.productUUID) { return true } + } else { - if (!this.pendingProduct.description) { - return true + for (let field of this.pendingProductRequiredFields) { + if (!this.pendingProduct[field]) { + return true + } } } + if (!this.productUOM) { return true } + return false }, - - itemDialogSaveButtonText() { - if (this.itemDialogSaving) { - return "Working, please wait..." - } - return this.editingItem ? "Update Item" : "Add Item" - }, - - submitOrderButtonText() { - if (this.submittingOrder) { - return "Working, please wait..." - } - return "Submit this Order" - }, }, mounted() { if (this.customerStatusType) { @@ -1925,11 +1962,14 @@ this.productIsKnown = !!row.product_uuid this.productUUID = row.product_uuid - this.pendingProduct = {} + + // nb. must construct new object before updating data + // (otherwise vue does not notice the changes?) + let pending = {} if (row.pending_product) { - this.copyPendingProductAttrs(row.pending_product, - this.pendingProduct) + this.copyPendingProductAttrs(row.pending_product, pending) } + this.pendingProduct = pending this.productDisplay = row.product_full_description this.productKey = row.product_key diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index 60949e8f..cc02f682 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -102,6 +102,19 @@ class CustomerOrderView(MasterView): 'flagged', ] + PENDING_PRODUCT_ENTRY_FIELDS = [ + 'key', + 'department_uuid', + 'brand_name', + 'description', + 'size', + 'vendor_name', + 'vendor_item_code', + 'unit_cost', + 'case_size', + 'regular_price_amount', + ] + def __init__(self, request): super(CustomerOrderView, self).__init__(request) self.batch_handler = self.get_batch_handler() @@ -361,6 +374,7 @@ class CustomerOrderView(MasterView): 'order_items': items, 'product_key_label': app.get_product_key_label(), 'allow_unknown_product': self.batch_handler.allow_unknown_product(), + 'pending_product_required_fields': self.get_pending_product_required_fields(), 'department_options': self.get_department_options(), 'default_uom_choices': self.batch_handler.uom_choices_for_product(None), 'default_uom': None, @@ -390,6 +404,17 @@ class CustomerOrderView(MasterView): 'value': department.uuid}) return options + def get_pending_product_required_fields(self): + required = [] + for field in self.PENDING_PRODUCT_ENTRY_FIELDS: + require = self.rattail_config.getbool('rattail.custorders', + f'unknown_product.fields.{field}.required') + if require is None and field == 'description': + require = True + if require: + required.append(field) + return required + def get_current_batch(self): user = self.request.user if not user: @@ -1044,7 +1069,7 @@ class CustomerOrderView(MasterView): } def configure_get_simple_settings(self): - return [ + settings = [ # customer handling {'section': 'rattail.custorders', @@ -1067,9 +1092,6 @@ class CustomerOrderView(MasterView): {'section': 'rattail.custorders', 'option': 'product_price_may_be_questionable', 'type': bool}, - {'section': 'rattail.custorders', - 'option': 'allow_unknown_product', - 'type': bool}, {'section': 'rattail.custorders', 'option': 'allow_item_discounts', 'type': bool}, @@ -1082,8 +1104,30 @@ class CustomerOrderView(MasterView): {'section': 'rattail.custorders', 'option': 'allow_past_item_reorder', 'type': bool}, + + # unknown products + {'section': 'rattail.custorders', + 'option': 'allow_unknown_product', + 'type': bool}, ] + for field in self.PENDING_PRODUCT_ENTRY_FIELDS: + setting = {'section': 'rattail.custorders', + 'option': f'unknown_product.fields.{field}.required', + 'type': bool} + if field == 'description': + setting['default'] = True + settings.append(setting) + + return settings + + def configure_get_context(self, **kwargs): + context = super().configure_get_context(**kwargs) + + context['pending_product_fields'] = self.PENDING_PRODUCT_ENTRY_FIELDS + + return context + @classmethod def defaults(cls, config): cls._order_defaults(config) From 72dda3771ede6c4ac0822a821f9ced57deca814a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Tue, 24 Oct 2023 19:51:27 -0500 Subject: [PATCH 004/398] Add price confirm prompt when adding unknown item to custorder optional, per config --- tailbone/templates/custorders/configure.mako | 12 ++- tailbone/templates/custorders/create.mako | 90 +++++++++++++++++++- tailbone/views/custorders/orders.py | 5 ++ 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/tailbone/templates/custorders/configure.mako b/tailbone/templates/custorders/configure.mako index 3f7041d3..d2f6610d 100644 --- a/tailbone/templates/custorders/configure.mako +++ b/tailbone/templates/custorders/configure.mako @@ -140,7 +140,8 @@ Require these fields for new product:

-
+
% for field in pending_product_fields: + + + Require price confirmation + + +
diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index dbcd81b3..f666790e 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -757,6 +757,12 @@
+ + + {{ pendingProductGrossMargin }} + + + @@ -905,6 +911,52 @@ + % if unknown_product_confirm_price: + + + + % endif + @@ -1242,6 +1294,9 @@ pendingProduct: {}, pendingProductRequiredFields: ${json.dumps(pending_product_required_fields)|n}, departmentOptions: ${json.dumps(department_options)|n}, + % if unknown_product_confirm_price: + confirmPriceShowDialog: false, + % endif submittingOrder: false, } @@ -1428,6 +1483,15 @@ % endif + pendingProductGrossMargin() { + let cost = this.pendingProduct.unit_cost + let price = this.pendingProduct.regular_price_amount + if (cost && price) { + let margin = (price - cost) / price + return (100 * margin).toFixed(2).toString() + " %" + } + }, + itemDialogSaveDisabled() { if (this.itemDialogSaving) { @@ -2116,7 +2180,7 @@ } }, - itemDialogSave() { + itemDialogAttemptSave() { this.itemDialogSaving = true let params = { @@ -2168,6 +2232,30 @@ this.itemDialogSaving = false }) }, + + itemDialogSave() { + + % if unknown_product_confirm_price: + if (!this.productIsKnown && !this.editingItem) { + this.showingItemDialog = false + this.confirmPriceShowDialog = true + return + } + % endif + + this.itemDialogAttemptSave() + }, + + confirmPriceCancel() { + this.confirmPriceShowDialog = false + this.showingItemDialog = true + }, + + confirmPriceSave() { + this.confirmPriceShowDialog = false + this.showingItemDialog = true + this.itemDialogAttemptSave() + }, }, } diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index cc02f682..c91ff4d2 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -375,6 +375,8 @@ class CustomerOrderView(MasterView): 'product_key_label': app.get_product_key_label(), 'allow_unknown_product': self.batch_handler.allow_unknown_product(), 'pending_product_required_fields': self.get_pending_product_required_fields(), + 'unknown_product_confirm_price': self.rattail_config.getbool( + 'rattail.custorders', 'unknown_product.always_confirm_price'), 'department_options': self.get_department_options(), 'default_uom_choices': self.batch_handler.uom_choices_for_product(None), 'default_uom': None, @@ -1109,6 +1111,9 @@ class CustomerOrderView(MasterView): {'section': 'rattail.custorders', 'option': 'allow_unknown_product', 'type': bool}, + {'section': 'rattail.custorders', + 'option': 'unknown_product.always_confirm_price', + 'type': bool}, ] for field in self.PENDING_PRODUCT_ENTRY_FIELDS: From 70cc754f3e871d0fff64f8cfaea8cb90fb4c266b Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 25 Oct 2023 10:45:33 -0500 Subject: [PATCH 005/398] Use `` for theme picker instead of webhelpers2.html.tags.select() which seems to break for me in dev now with python 3.10 --- tailbone/static/css/layout.css | 4 ---- tailbone/subscribers.py | 2 +- tailbone/templates/base.mako | 22 ++++++++++++++++------ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/tailbone/static/css/layout.css b/tailbone/static/css/layout.css index bdf35410..20dbf6b7 100644 --- a/tailbone/static/css/layout.css +++ b/tailbone/static/css/layout.css @@ -57,10 +57,6 @@ header span.header-text { margin-right: 10px; } -header .level .theme-picker { - display: inline-flex; -} - #content-title h1 { margin-bottom: 0; margin-right: 1rem; diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py index b724a4c5..1143b510 100644 --- a/tailbone/subscribers.py +++ b/tailbone/subscribers.py @@ -158,7 +158,7 @@ def before_render(event): default=['falafel']) if 'default' not in available: available.insert(0, 'default') - options = [tags.Option(theme) for theme in available] + options = [tags.Option(theme, value=theme) for theme in available] renderer_globals['theme_picker_options'] = options # heck while we're assuming the classic web app here... diff --git a/tailbone/templates/base.mako b/tailbone/templates/base.mako index 8558eeb7..53dc3423 100644 --- a/tailbone/templates/base.mako +++ b/tailbone/templates/base.mako @@ -392,13 +392,19 @@ % if expose_theme_picker and request.has_perm('common.change_app_theme'):
${h.form(url('change_theme'), method="post", ref='themePickerForm')} - ${h.csrf_token(request)} - Theme: -
-
- ${h.select('theme', theme, theme_picker_options, **{'@change': 'changeTheme()'})} + ${h.csrf_token(request)} +
+ Theme: + + % for option in theme_picker_options: + + % endfor +
-
${h.end_form()}
% endif @@ -840,6 +846,10 @@ contentTitleHTML: ${json.dumps(capture(self.content_title))|n}, feedbackMessage: "", + % if expose_theme_picker and request.has_perm('common.change_app_theme'): + globalTheme: ${json.dumps(theme)|n}, + % endif + % if can_edit_help: configureFieldsHelp: false, % endif From cf1ef2399626a46bf44efd6229a8427e4865304a Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 25 Oct 2023 11:40:52 -0500 Subject: [PATCH 006/398] Add `column_only` kwarg for `Grid.set_label()` method pass True to affect only the column label and not the filter --- tailbone/grids/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py index 5f28fca0..7a0d00e3 100644 --- a/tailbone/grids/core.py +++ b/tailbone/grids/core.py @@ -385,9 +385,9 @@ class Grid(object): def remove_filter(self, key): self.filters.pop(key, None) - def set_label(self, key, label): + def set_label(self, key, label, column_only=False): self.labels[key] = label - if key in self.filters: + if not column_only and key in self.filters: self.filters[key].label = label def get_label(self, key): From b5c68831b55d299f0d613626da2fed5fda791d09 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 25 Oct 2023 12:20:04 -0500 Subject: [PATCH 007/398] Do not show profile buttons for inactive customer shoppers --- tailbone/views/customers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py index 668f4a2b..0d4e3d7c 100644 --- a/tailbone/views/customers.py +++ b/tailbone/views/customers.py @@ -424,8 +424,9 @@ class CustomerView(MasterView): people.setdefault(person.uuid, person) for shopper in customer.shoppers: - person = shopper.person - people.setdefault(person.uuid, person) + if shopper.active: + person = shopper.person + people.setdefault(person.uuid, person) for person in customer.people: people.setdefault(person.uuid, person) From 441a6e5e0c00e3cbdc846648253a9442e3fa9483 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 25 Oct 2023 14:06:40 -0500 Subject: [PATCH 008/398] Add separate perm for making new custorder for unknown product --- tailbone/views/custorders/orders.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index c91ff4d2..f76d4d93 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -373,7 +373,8 @@ class CustomerOrderView(MasterView): 'allow_contact_info_create': self.batch_handler.allow_contact_info_creation(), 'order_items': items, 'product_key_label': app.get_product_key_label(), - 'allow_unknown_product': self.batch_handler.allow_unknown_product(), + 'allow_unknown_product': (self.batch_handler.allow_unknown_product() + and self.has_perm('create_unknown_product')), 'pending_product_required_fields': self.get_pending_product_required_fields(), 'unknown_product_confirm_price': self.rattail_config.getbool( 'rattail.custorders', 'unknown_product.always_confirm_price'), @@ -1143,8 +1144,15 @@ class CustomerOrderView(MasterView): route_prefix = cls.get_route_prefix() url_prefix = cls.get_url_prefix() model_title = cls.get_model_title() + model_title_plural = cls.get_model_title_plural() permission_prefix = cls.get_permission_prefix() + config.add_tailbone_permission_group(permission_prefix, model_title_plural, overwrite=False) + + config.add_tailbone_permission(permission_prefix, + f'{permission_prefix}.create_unknown_product', + f"Create new {model_title} for unknown product") + # add pseudo-index page for creating new custorder # (makes it available when building menus etc.) config.add_tailbone_index_page('{}.create'.format(route_prefix), From a8121814660665011404e970e19560139e24edda Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 25 Oct 2023 20:10:21 -0500 Subject: [PATCH 009/398] Expand the "product lookup" component to include autocomplete --- tailbone/templates/custorders/create.mako | 87 ++++++------- tailbone/templates/products/lookup.mako | 141 ++++++++++++++++++---- 2 files changed, 155 insertions(+), 73 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index f666790e..86a5e804 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -531,33 +531,10 @@

Product

- - - - - - - Full Lookup - - - - View Product - + +
@@ -565,7 +542,6 @@
- ##

{{ productKey }}

@@ -957,11 +933,6 @@ % endif - - - % if allow_past_item_reorder:
@@ -1258,6 +1229,7 @@ pastItemsSelected: null, % endif productIsKnown: true, + selectedProduct: null, productUUID: null, productDisplay: null, productKey: null, @@ -1544,6 +1516,18 @@ this.$refs.contactAutocomplete.clearSelection() } }, + + productIsKnown(newval, oldval) { + // TODO: seems like this should be better somehow? + // e.g. maybe we should not be clearing *everything* + // in case user accidentally clicks, and then clicks + // "is known" again? and if we *should* clear all, + // why does that require 2 steps? + if (!newval) { + this.selectedProduct = null + this.clearProduct() + } + }, }, methods: { @@ -1894,20 +1878,12 @@ } }, - productFullLookup() { - this.showingItemDialog = false - let term = this.$refs.productAutocomplete.getUserInput() - this.$refs.productLookup.showDialog(term) - }, - - productLookupCanceled() { - this.showingItemDialog = true - }, - productLookupSelected(selected) { + // TODO: this still is a hack somehow, am sure of it. + // need to clean this up at some point + this.selectedProduct = selected this.clearProduct() - this.productChanged(selected.uuid) - this.showingItemDialog = true + this.productChanged(selected) }, copyPendingProductAttrs(from, to) { @@ -1930,6 +1906,7 @@ this.customerPanelOpen = false this.editingItem = null this.productIsKnown = true + this.selectedProduct = null this.productUUID = null this.productDisplay = null this.productKey = null @@ -1962,7 +1939,7 @@ this.itemDialogTabIndex = 0 this.showingItemDialog = true this.$nextTick(() => { - this.$refs.productAutocomplete.focus() + this.$refs.productLookup.focus() }) }, @@ -2027,6 +2004,16 @@ this.productIsKnown = !!row.product_uuid this.productUUID = row.product_uuid + if (row.product_uuid) { + this.selectedProduct = { + uuid: row.product_uuid, + full_description: row.product_full_description, + url: row.product_url, + } + } else { + this.selectedProduct = null + } + // nb. must construct new object before updating data // (otherwise vue does not notice the changes?) let pending = {} @@ -2131,11 +2118,11 @@ } }, - productChanged(uuid) { - if (uuid) { + productChanged(product) { + if (product) { let params = { action: 'get_product_info', - uuid: uuid, + uuid: product.uuid, } // nb. it is possible for the handler to "swap" // the product selection, i.e. user chooses a "per @@ -2144,6 +2131,8 @@ // received above is the correct one, but just use // whatever came back from handler this.submitBatchData(params, response => { + this.selectedProduct = response.data + this.productUUID = response.data.uuid this.productKey = response.data.key this.productDisplay = response.data.full_description diff --git a/tailbone/templates/products/lookup.mako b/tailbone/templates/products/lookup.mako index cdc4c565..42ee0742 100644 --- a/tailbone/templates/products/lookup.mako +++ b/tailbone/templates/products/lookup.mako @@ -2,8 +2,49 @@ <%def name="tailbone_product_lookup_template()"> @@ -166,9 +208,17 @@ const TailboneProductLookup = { template: '#tailbone-product-lookup-template', + props: { + selectedProduct: { + type: Object, + }, + }, data() { return { - showingDialog: false, + autocompleteValue: '', + autocompleteOptions: [], + + lookupShowDialog: false, searchTerm: null, searchTermLastUsed: null, @@ -187,23 +237,67 @@ }, methods: { - showDialog(term) { + focus() { + if (!this.selectedProduct) { + this.$refs.productAutocomplete.focus() + } + }, + clearSelection(focus) { + + // clear data + this.autocompleteValue = '' + this.$emit('selected', null) + + // maybe set focus to our (autocomplete) component + if (focus) { + this.$nextTick(() => { + this.focus() + }) + } + }, + + getAutocompleteOptions: debounce(function (entry) { + + // since the `@typing` event from buefy component does not + // "self-regulate" in any way, we a) use `debounce` above, + // but also b) skip the search unless we have at least 3 + // characters of input from user + if (entry.length < 3) { + this.data = [] + return + } + + // and perform the search + let url = '${url(f'{route_prefix}.product_autocomplete')}' + this.$http.get(url + '?term=' + encodeURIComponent(entry)) + .then(({ data }) => { + this.autocompleteOptions = data + }).catch((error) => { + this.autocompleteOptions = [] + throw error + }) + }), + + autocompleteSelected(option) { + this.$emit('selected', { + uuid: option.value, + full_description: option.label, + }) + }, + + lookupInit() { this.searchResultSelected = null + this.lookupShowDialog = true - if (term !== undefined) { - this.searchTerm = term - // perform search if invoked with new term - if (term != this.searchTermLastUsed) { + this.$nextTick(() => { + + this.searchTerm = this.autocompleteValue + if (this.searchTerm != this.searchTermLastUsed) { this.searchTermLastUsed = null this.performSearch() } - } else { - this.searchTerm = this.searchTermLastUsed - } - this.showingDialog = true - this.$nextTick(() => { this.$refs.searchTermInput.focus() }) }, @@ -214,17 +308,6 @@ } }, - cancelDialog() { - this.searchResultSelected = null - this.showingDialog = false - this.$emit('canceled') - }, - - selectResult() { - this.showingDialog = false - this.$emit('selected', this.searchResultSelected) - }, - performSearch() { if (this.searchResultsLoading) { return @@ -255,6 +338,16 @@ this.searchResultsLoading = false }) }, + + selectResult() { + this.lookupShowDialog = false + this.$emit('selected', this.searchResultSelected) + }, + + cancelDialog() { + this.searchResultSelected = null + this.lookupShowDialog = false + }, }, } From 4809cf039e9925d64f19b75e6467cb8de1e74f72 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 25 Oct 2023 20:22:48 -0500 Subject: [PATCH 010/398] Update changelog --- CHANGES.rst | 22 ++++++++++++++++++++++ tailbone/_version.py | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 06db3d61..03c89807 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,28 @@ CHANGELOG ========= +0.9.71 (2023-10-25) +------------------- + +* Fix bug when editing vendor. + +* Show user warning if "add item to custorder" fails. + +* Allow pending product fields to be required, for new custorder. + +* Add price confirm prompt when adding unknown item to custorder. + +* Use ```` for theme picker. + +* Add ``column_only`` kwarg for ``Grid.set_label()`` method. + +* Do not show profile buttons for inactive customer shoppers. + +* Add separate perm for making new custorder for unknown product. + +* Expand the "product lookup" component to include autocomplete. + + 0.9.70 (2023-10-24) ------------------- diff --git a/tailbone/_version.py b/tailbone/_version.py index deda170c..4477c9fb 100644 --- a/tailbone/_version.py +++ b/tailbone/_version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8; -*- -__version__ = '0.9.70' +__version__ = '0.9.71' From a5c1cba81bb68394f3b54d42a29da84d1fb25715 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 26 Oct 2023 10:06:00 -0500 Subject: [PATCH 011/398] Use product lookup component for "resolve pending product" tool --- tailbone/templates/custorders/create.mako | 5 +-- tailbone/templates/products/lookup.mako | 29 ++++++++++------- tailbone/templates/products/pending/view.mako | 31 +++++++++++++++---- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako index 86a5e804..399c1a6b 100644 --- a/tailbone/templates/custorders/create.mako +++ b/tailbone/templates/custorders/create.mako @@ -532,8 +532,9 @@ Product

+ :product="selectedProduct" + @selected="productLookupSelected" + autocomplete-url="${url(f'{route_prefix}.product_autocomplete')}"> diff --git a/tailbone/templates/products/lookup.mako b/tailbone/templates/products/lookup.mako index 42ee0742..4e8c3a8b 100644 --- a/tailbone/templates/products/lookup.mako +++ b/tailbone/templates/products/lookup.mako @@ -6,9 +6,9 @@ - + - - {{ selectedProduct.full_description }} + {{ product.full_description }} Full Lookup - View Product @@ -209,9 +209,13 @@ const TailboneProductLookup = { template: '#tailbone-product-lookup-template', props: { - selectedProduct: { + product: { type: Object, }, + autocompleteUrl: { + type: String, + default: '${url('products.autocomplete')}', + }, }, data() { return { @@ -238,7 +242,7 @@ methods: { focus() { - if (!this.selectedProduct) { + if (!this.product) { this.$refs.productAutocomplete.focus() } }, @@ -269,8 +273,7 @@ } // and perform the search - let url = '${url(f'{route_prefix}.product_autocomplete')}' - this.$http.get(url + '?term=' + encodeURIComponent(entry)) + this.$http.get(this.autocompleteUrl + '?term=' + encodeURIComponent(entry)) .then(({ data }) => { this.autocompleteOptions = data }).catch((error) => { @@ -283,6 +286,8 @@ this.$emit('selected', { uuid: option.value, full_description: option.label, + url: option.url, + image_url: option.image_url, }) }, diff --git a/tailbone/templates/products/pending/view.mako b/tailbone/templates/products/pending/view.mako index 2b9852d9..e3740c71 100644 --- a/tailbone/templates/products/pending/view.mako +++ b/tailbone/templates/products/pending/view.mako @@ -1,5 +1,11 @@ ## -*- coding: utf-8; -*- <%inherit file="/master/view.mako" /> +<%namespace name="product_lookup" file="/products/lookup.mako" /> + +<%def name="render_this_page_template()"> + ${parent.render_this_page_template()} + ${product_lookup.tailbone_product_lookup_template()} + <%def name="object_helpers()"> ${parent.object_helpers()} @@ -43,12 +49,13 @@ ${instance.full_description} - - + + + ${h.hidden('product_uuid', **{':value': 'resolveProductUUID'})}