diff --git a/tailbone/static/js/tailbone.buefy.autocomplete.js b/tailbone/static/js/tailbone.buefy.autocomplete.js
index f615c2a9..b4070fab 100644
--- a/tailbone/static/js/tailbone.buefy.autocomplete.js
+++ b/tailbone/static/js/tailbone.buefy.autocomplete.js
@@ -223,6 +223,12 @@ const TailboneAutocomplete = {
// we have nothing to go on here..
return ""
},
+
+ // returns the "raw" user input from the underlying buefy
+ // autocomplete component
+ getUserInput() {
+ return this.buefyValue
+ },
},
}
diff --git a/tailbone/templates/custorders/create.mako b/tailbone/templates/custorders/create.mako
index c3aed270..ddabfc4d 100644
--- a/tailbone/templates/custorders/create.mako
+++ b/tailbone/templates/custorders/create.mako
@@ -1,5 +1,6 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/create.mako" />
+<%namespace name="product_lookup" file="/products/lookup.mako" />
<%def name="extra_styles()">
${parent.extra_styles()}
@@ -54,6 +55,7 @@
<%def name="render_this_page_template()">
${parent.render_this_page_template()}
+ ${product_lookup.tailbone_product_lookup_template()}
+%def>
+
+<%def name="tailbone_product_lookup_component()">
+
+%def>
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index 15b2083f..752a996d 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -179,9 +179,10 @@ class ProductView(MasterView):
'tailbone', 'products.print_labels', default=False)
app = self.get_rattail_app()
- self.product_handler = app.get_products_handler()
- # TODO: deprecate / remove this
- self.handler = self.product_handler
+ self.products_handler = app.get_products_handler()
+ # TODO: deprecate / remove these
+ self.product_handler = self.products_handler
+ self.handler = self.products_handler
def query(self, session):
user = self.request.user
@@ -535,7 +536,7 @@ class ProductView(MasterView):
if not product.not_for_sale:
price = product[field]
if price:
- return self.product_handler.render_price(price)
+ return self.products_handler.render_price(price)
def render_current_price_for_grid(self, product, field):
text = self.render_price(product, field) or ""
@@ -1173,7 +1174,7 @@ class ProductView(MasterView):
key = self.rattail_config.product_key()
kwargs['product_key_field'] = self.product_key_fields.get(key, key)
- kwargs['image_url'] = self.product_handler.get_image_url(product)
+ kwargs['image_url'] = self.products_handler.get_image_url(product)
# add price history, if user has access
if self.rattail_config.versioning_enabled() and self.has_perm('versions'):
@@ -1743,6 +1744,105 @@ class ProductView(MasterView):
return {'ok': True}
def search(self):
+ """
+ Perform a product search across multiple fields, and return
+ the results as JSON suitable for row data for a Buefy
+ ```` component.
+ """
+ if 'term' not in self.request.GET:
+ # TODO: deprecate / remove this? not sure if/where it is used
+ return self.search_v1()
+
+ term = self.request.GET.get('term')
+ if not term:
+ return {'ok': True, 'results': []}
+
+ supported_fields = [
+ 'product_key',
+ 'vendor_code',
+ 'alt_code',
+ 'brand_name',
+ 'description',
+ ]
+
+ search_fields = []
+ for field in supported_fields:
+ key = 'search_{}'.format(field)
+ if self.request.GET.get(key) == 'true':
+ search_fields.append(field)
+
+ final_results = []
+ session = self.Session()
+ model = self.model
+
+ lookup_fields = []
+ if 'product_key' in search_fields:
+ lookup_fields.append('_product_key_')
+ if 'vendor_code' in search_fields:
+ lookup_fields.append('vendor_code')
+ if 'alt_code' in search_fields:
+ lookup_fields.append('alt_code')
+ if lookup_fields:
+ product = self.products_handler.locate_product_for_entry(
+ session, term, lookup_fields=lookup_fields)
+ if product:
+ final_results.append(self.search_normalize_result(product))
+
+ # base wildcard query
+ query = session.query(model.Product)
+ if 'brand_name' in search_fields:
+ query = query.outerjoin(model.Brand)
+
+ # now figure out wildcard criteria
+ criteria = []
+ for word in term.split():
+ if 'brand_name' in search_fields and 'description' in search_fields:
+ criteria.append(sa.or_(
+ model.Brand.name.ilike('%{}%'.format(word)),
+ model.Product.description.ilike('%{}%'.format(word))))
+ elif 'brand_name' in search_fields:
+ criteria.append(model.Brand.name.ilike('%{}%'.format(word)))
+ elif 'description' in search_fields:
+ criteria.append(model.Product.description.ilike('%{}%'.format(word)))
+
+ # execute wildcard query if applicable
+ max_results = 30 # TODO: make conifgurable?
+ elided = 0
+ if criteria:
+ query = query.filter(sa.and_(*criteria))
+ count = query.count()
+ if count > max_results:
+ elided = count - max_results
+ for product in query[:max_results]:
+ final_results.append(self.search_normalize_result(product))
+
+ return {'ok': True, 'results': final_results, 'elided': elided}
+
+ def search_normalize_result(self, product, **kwargs):
+ return self.products_handler.normalize_product(product, fields=[
+ 'product_key',
+ 'url',
+ 'image_url',
+ 'brand_name',
+ 'description',
+ 'size',
+ 'full_description',
+ 'department_name',
+ 'unit_price',
+ 'unit_price_display',
+ 'sale_price',
+ 'sale_price_display',
+ 'sale_ends_display',
+ 'vendor_name',
+ # TODO: should be case_size
+ 'case_quantity',
+ 'case_price',
+ 'case_price_display',
+ 'uom_choices',
+ ])
+
+ # TODO: deprecate / remove this? not sure if/where it is used
+ def search_v1(self):
"""
Locate a product(s) by UPC.
@@ -2027,10 +2127,10 @@ class ProductView(MasterView):
renderer='{}/batch.mako'.format(template_prefix),
permission='{}.make_batch'.format(permission_prefix))
- # search (by upc)
+ # search
config.add_route('products.search', '/products/search')
config.add_view(cls, attr='search', route_name='products.search',
- renderer='json', permission='products.view')
+ renderer='json', permission='products.list')
# product image
config.add_route('products.image', '/products/{uuid}/image')