diff --git a/pyproject.toml b/pyproject.toml
index 4b04493..f250b63 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,15 +49,9 @@ sideshow_libcache = "sideshow.web.static:libcache"
[project.entry-points."paste.app_factory"]
"main" = "sideshow.web.app:main"
-[project.entry-points."wutta.batch.neworder"]
-"sideshow" = "sideshow.batch.neworder:NewOrderBatchHandler"
-
[project.entry-points."wutta.config.extensions"]
"sideshow" = "sideshow.config:SideshowConfig"
-[project.entry-points."wutta.web.menus"]
-sideshow = "sideshow.web.menus:SideshowMenuHandler"
-
[project.urls]
Homepage = "https://wuttaproject.org/"
diff --git a/src/sideshow/batch/neworder.py b/src/sideshow/batch/neworder.py
index e2ce3a4..353af9c 100644
--- a/src/sideshow/batch/neworder.py
+++ b/src/sideshow/batch/neworder.py
@@ -77,65 +77,6 @@ class NewOrderBatchHandler(BatchHandler):
return self.config.get_bool('sideshow.orders.allow_unknown_products',
default=True)
- def autocomplete_customers_external(self, session, term, user=None):
- """
- Return autocomplete search results for :term:`external
- customer` records.
-
- There is no default logic here; subclass must implement.
-
- :param session: Current app :term:`db session`.
-
- :param term: Search term string from user input.
-
- :param user:
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
- is doing the search, if known.
-
- :returns: List of search results; each should be a dict with
- ``value`` and ``label`` keys.
- """
- raise NotImplementedError
-
- def autocomplete_customers_local(self, session, term, user=None):
- """
- Return autocomplete search results for
- :class:`~sideshow.db.model.customers.LocalCustomer` records.
-
- :param session: Current app :term:`db session`.
-
- :param term: Search term string from user input.
-
- :param user:
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
- is doing the search, if known.
-
- :returns: List of search results; each should be a dict with
- ``value`` and ``label`` keys.
- """
- model = self.app.model
-
- # base query
- query = session.query(model.LocalCustomer)
-
- # filter query
- criteria = [model.LocalCustomer.full_name.ilike(f'%{word}%')
- for word in term.split()]
- query = query.filter(sa.and_(*criteria))
-
- # sort query
- query = query.order_by(model.LocalCustomer.full_name)
-
- # get data
- # TODO: need max_results option
- customers = query.all()
-
- # get results
- def result(customer):
- return {'value': customer.uuid.hex,
- 'label': customer.full_name}
- return [result(c) for c in customers]
-
def set_customer(self, batch, customer_info, user=None):
"""
Set/update customer info for the batch.
@@ -150,14 +91,14 @@ class NewOrderBatchHandler(BatchHandler):
:class:`~sideshow.db.model.customers.PendingCustomer` record
is created if necessary.
- And then it will update customer-related attributes via one of:
+ And then it will update these accordingly:
- * :meth:`refresh_batch_from_external_customer()`
- * :meth:`refresh_batch_from_local_customer()`
- * :meth:`refresh_batch_from_pending_customer()`
+ * :attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.customer_name`
+ * :attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.phone_number`
+ * :attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.email_address`
Note that ``customer_info`` may be ``None``, which will cause
- customer attributes to be set to ``None`` also.
+ all the above to be set to ``None`` also.
:param batch:
:class:`~sideshow.db.model.batch.neworder.NewOrderBatch` to
@@ -188,11 +129,13 @@ class NewOrderBatchHandler(BatchHandler):
if not customer:
raise ValueError("local customer not found")
batch.local_customer = customer
- self.refresh_batch_from_local_customer(batch)
+ batch.customer_name = customer.full_name
+ batch.phone_number = customer.phone_number
+ batch.email_address = customer.email_address
else: # external customer_id
- batch.customer_id = customer_info
- self.refresh_batch_from_external_customer(batch)
+ #batch.customer_id = customer_info
+ raise NotImplementedError
elif customer_info:
@@ -217,7 +160,9 @@ class NewOrderBatchHandler(BatchHandler):
if 'full_name' not in customer_info:
customer.full_name = self.app.make_full_name(customer.first_name,
customer.last_name)
- self.refresh_batch_from_pending_customer(batch)
+ batch.customer_name = customer.full_name
+ batch.phone_number = customer.phone_number
+ batch.email_address = customer.email_address
else:
@@ -230,203 +175,6 @@ class NewOrderBatchHandler(BatchHandler):
session.flush()
- def refresh_batch_from_external_customer(self, batch):
- """
- Update customer-related attributes on the batch, from its
- :term:`external customer` record.
-
- This is called automatically from :meth:`set_customer()`.
-
- There is no default logic here; subclass must implement.
- """
- raise NotImplementedError
-
- def refresh_batch_from_local_customer(self, batch):
- """
- Update customer-related attributes on the batch, from its
- :attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.local_customer`
- record.
-
- This is called automatically from :meth:`set_customer()`.
- """
- customer = batch.local_customer
- batch.customer_name = customer.full_name
- batch.phone_number = customer.phone_number
- batch.email_address = customer.email_address
-
- def refresh_batch_from_pending_customer(self, batch):
- """
- Update customer-related attributes on the batch, from its
- :attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.pending_customer`
- record.
-
- This is called automatically from :meth:`set_customer()`.
- """
- customer = batch.pending_customer
- batch.customer_name = customer.full_name
- batch.phone_number = customer.phone_number
- batch.email_address = customer.email_address
-
- def autocomplete_products_external(self, session, term, user=None):
- """
- Return autocomplete search results for :term:`external
- product` records.
-
- There is no default logic here; subclass must implement.
-
- :param session: Current app :term:`db session`.
-
- :param term: Search term string from user input.
-
- :param user:
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
- is doing the search, if known.
-
- :returns: List of search results; each should be a dict with
- ``value`` and ``label`` keys.
- """
- raise NotImplementedError
-
- def autocomplete_products_local(self, session, term, user=None):
- """
- Return autocomplete search results for
- :class:`~sideshow.db.model.products.LocalProduct` records.
-
- :param session: Current app :term:`db session`.
-
- :param term: Search term string from user input.
-
- :param user:
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
- is doing the search, if known.
-
- :returns: List of search results; each should be a dict with
- ``value`` and ``label`` keys.
- """
- model = self.app.model
-
- # base query
- query = session.query(model.LocalProduct)
-
- # filter query
- criteria = []
- for word in term.split():
- criteria.append(sa.or_(
- model.LocalProduct.brand_name.ilike(f'%{word}%'),
- model.LocalProduct.description.ilike(f'%{word}%')))
- query = query.filter(sa.and_(*criteria))
-
- # sort query
- query = query.order_by(model.LocalProduct.brand_name,
- model.LocalProduct.description)
-
- # get data
- # TODO: need max_results option
- products = query.all()
-
- # get results
- def result(product):
- return {'value': product.uuid.hex,
- 'label': product.full_description}
- return [result(c) for c in products]
-
- def get_product_info_external(self, session, product_id, user=None):
- """
- Returns basic info for an :term:`external product` as pertains
- to ordering.
-
- When user has located a product via search, and must then
- choose order quantity and UOM based on case size, pricing
- etc., this method is called to retrieve the product info.
-
- There is no default logic here; subclass must implement.
-
- :param session: Current app :term:`db session`.
-
- :param product_id: Product ID string for which to retrieve
- info.
-
- :param user:
- :class:`~wuttjamaican:wuttjamaican.db.model.auth.User` who
- is performing the action, if known.
-
- :returns: Dict of product info. Should raise error instead of
- returning ``None`` if product not found.
-
- This method should only be called after a product has been
- identified via autocomplete/search lookup; therefore the
- ``product_id`` should be valid, and the caller can expect this
- method to *always* return a dict. If for some reason the
- product cannot be found here, an error should be raised.
-
- The dict should contain as much product info as is available
- and needed; if some are missing it should not cause too much
- trouble in the app. Here is a basic example::
-
- def get_product_info_external(self, session, product_id, user=None):
- ext_model = get_external_model()
- ext_session = make_external_session()
-
- ext_product = ext_session.get(ext_model.Product, product_id)
- if not ext_product:
- ext_session.close()
- raise ValueError(f"external product not found: {product_id}")
-
- info = {
- 'product_id': product_id,
- 'scancode': product.scancode,
- 'brand_name': product.brand_name,
- 'description': product.description,
- 'size': product.size,
- 'weighed': product.sold_by_weight,
- 'special_order': False,
- 'department_id': str(product.department_number),
- 'department_name': product.department_name,
- 'case_size': product.case_size,
- 'unit_price_reg': product.unit_price_reg,
- 'vendor_name': product.vendor_name,
- 'vendor_item_code': product.vendor_item_code,
- }
-
- ext_session.close()
- return info
- """
- raise NotImplementedError
-
- def get_product_info_local(self, session, uuid, user=None):
- """
- Returns basic info for a
- :class:`~sideshow.db.model.products.LocalProduct` as pertains
- to ordering.
-
- When user has located a product via search, and must then
- choose order quantity and UOM based on case size, pricing
- etc., this method is called to retrieve the product info.
-
- See :meth:`get_product_info_external()` for more explanation.
- """
- model = self.app.model
- product = session.get(model.LocalProduct, uuid)
- if not product:
- raise ValueError(f"Local Product not found: {uuid}")
-
- return {
- 'product_id': product.uuid.hex,
- 'scancode': product.scancode,
- 'brand_name': product.brand_name,
- 'description': product.description,
- 'size': product.size,
- 'full_description': product.full_description,
- 'weighed': product.weighed,
- 'special_order': product.special_order,
- 'department_id': product.department_id,
- 'department_name': product.department_name,
- 'case_size': product.case_size,
- 'unit_price_reg': product.unit_price_reg,
- 'vendor_name': product.vendor_name,
- 'vendor_item_code': product.vendor_item_code,
- }
-
def add_item(self, batch, product_info, order_qty, order_uom, user=None):
"""
Add a new item/row to the batch, for given product and quantity.
@@ -476,7 +224,8 @@ class NewOrderBatchHandler(BatchHandler):
row.local_product = local
else: # external product_id
- row.product_id = product_info
+ #row.product_id = product_info
+ raise NotImplementedError
else:
# pending_product
@@ -564,7 +313,8 @@ class NewOrderBatchHandler(BatchHandler):
row.local_product = local
else: # external product_id
- row.product_id = product_info
+ #row.product_id = product_info
+ raise NotImplementedError
else:
# pending_product
@@ -743,7 +493,6 @@ class NewOrderBatchHandler(BatchHandler):
There is no default logic here; subclass must implement as
needed.
"""
- raise NotImplementedError
def remove_row(self, row):
"""
diff --git a/src/sideshow/config.py b/src/sideshow/config.py
index a010262..2a414aa 100644
--- a/src/sideshow/config.py
+++ b/src/sideshow/config.py
@@ -46,12 +46,8 @@ class SideshowConfig(WuttaConfigExtension):
config.setdefault(f'{config.appname}.model_spec', 'sideshow.db.model')
config.setdefault(f'{config.appname}.enum_spec', 'sideshow.enum')
- # batch handlers
- config.setdefault(f'{config.appname}.batch.neworder.handler.default_spec',
- 'sideshow.batch.neworder:NewOrderBatchHandler')
-
# web app menu
- config.setdefault(f'{config.appname}.web.menus.handler.default_spec',
+ config.setdefault(f'{config.appname}.web.menus.handler_spec',
'sideshow.web.menus:SideshowMenuHandler')
# web app libcache
diff --git a/src/sideshow/web/__init__.py b/src/sideshow/web/__init__.py
index b86b498..e69de29 100644
--- a/src/sideshow/web/__init__.py
+++ b/src/sideshow/web/__init__.py
@@ -1,31 +0,0 @@
-# -*- coding: utf-8; -*-
-################################################################################
-#
-# Sideshow -- Case/Special Order Tracker
-# Copyright © 2024 Lance Edgar
-#
-# This file is part of Sideshow.
-#
-# Sideshow is free software: you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Sideshow is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Sideshow. If not, see .
-#
-################################################################################
-"""
-Sideshow web app
-"""
-
-
-def includeme(config):
- config.include('sideshow.web.static')
- config.include('wuttaweb.subscribers')
- config.include('sideshow.web.views')
diff --git a/src/sideshow/web/app.py b/src/sideshow/web/app.py
index 77c28ab..66ff8c3 100644
--- a/src/sideshow/web/app.py
+++ b/src/sideshow/web/app.py
@@ -42,7 +42,9 @@ def main(global_config, **settings):
pyramid_config = base.make_pyramid_config(settings)
# bring in the rest of Sideshow
- pyramid_config.include('sideshow.web')
+ pyramid_config.include('sideshow.web.static')
+ pyramid_config.include('wuttaweb.subscribers')
+ pyramid_config.include('sideshow.web.views')
return pyramid_config.make_wsgi_app()
diff --git a/src/sideshow/web/menus.py b/src/sideshow/web/menus.py
index c8256fc..2c319cc 100644
--- a/src/sideshow/web/menus.py
+++ b/src/sideshow/web/menus.py
@@ -39,7 +39,6 @@ class SideshowMenuHandler(base.MenuHandler):
self.make_customers_menu(request),
self.make_products_menu(request),
self.make_batch_menu(request),
- self.make_other_menu(request),
self.make_admin_menu(request),
]
@@ -128,16 +127,6 @@ class SideshowMenuHandler(base.MenuHandler):
],
}
- def make_other_menu(self, request, **kwargs):
- """
- Generate the "Other" menu.
- """
- return {
- 'title': "Other",
- 'type': 'menu',
- 'items': [],
- }
-
def make_admin_menu(self, request, **kwargs):
""" """
kwargs['include_people'] = True
diff --git a/src/sideshow/web/templates/orders/configure.mako b/src/sideshow/web/templates/orders/configure.mako
index 10d58fa..4dc23f4 100644
--- a/src/sideshow/web/templates/orders/configure.mako
+++ b/src/sideshow/web/templates/orders/configure.mako
@@ -3,38 +3,15 @@
<%def name="form_content()">
-
Customers
-
-
-
-
-
-
-
-
-
-
Products
-
-
-
-
-
-
-
-
+
- Allow creating orders for new/unknown products
+ Allow creating orders for "unknown" products
@@ -61,32 +38,4 @@
-
- Batches
-
-
-
-
-
-
-
-
-
-
-%def>
-
-<%def name="modify_vue_vars()">
- ${parent.modify_vue_vars()}
-
%def>
diff --git a/src/sideshow/web/templates/orders/create.mako b/src/sideshow/web/templates/orders/create.mako
index 7ed78db..72345b5 100644
--- a/src/sideshow/web/templates/orders/create.mako
+++ b/src/sideshow/web/templates/orders/create.mako
@@ -836,11 +836,11 @@
orderEmailAddress: ${json.dumps(email_address)|n},
refreshingCustomer: false,
- newCustomerFullName: ${json.dumps(new_customer_full_name or None)|n},
- newCustomerFirstName: ${json.dumps(new_customer_first_name or None)|n},
- newCustomerLastName: ${json.dumps(new_customer_last_name or None)|n},
- newCustomerPhone: ${json.dumps(new_customer_phone or None)|n},
- newCustomerEmail: ${json.dumps(new_customer_email or None)|n},
+ newCustomerFullName: ${json.dumps(new_customer_full_name)|n},
+ newCustomerFirstName: ${json.dumps(new_customer_first_name)|n},
+ newCustomerLastName: ${json.dumps(new_customer_last_name)|n},
+ newCustomerPhone: ${json.dumps(new_customer_phone)|n},
+ newCustomerEmail: ${json.dumps(new_customer_email)|n},
editNewCustomerShowDialog: false,
editNewCustomerFirstName: null,
diff --git a/src/sideshow/web/views/orders.py b/src/sideshow/web/views/orders.py
index a48546e..bbd5811 100644
--- a/src/sideshow/web/views/orders.py
+++ b/src/sideshow/web/views/orders.py
@@ -57,11 +57,6 @@ class OrderView(MasterView):
Note that the "edit" view is not exposed here; user must perform
various other workflow actions to modify the order.
-
- .. attribute:: batch_handler
-
- Reference to the new order batch handler, as returned by
- :meth:`get_batch_handler()`. This gets set in the constructor.
"""
model_class = Order
editable = False
@@ -158,22 +153,6 @@ class OrderView(MasterView):
# total_price
g.set_renderer('total_price', g.render_currency)
- def get_batch_handler(self):
- """
- Returns the configured :term:`handler` for :term:`new order
- batches `.
-
- You normally would not need to call this, and can use
- :attr:`batch_handler` instead.
-
- :returns:
- :class:`~sideshow.batch.neworder.NewOrderBatchHandler`
- instance.
- """
- if hasattr(self, 'batch_handler'):
- return self.batch_handler
- return self.app.get_batch_handler('neworder')
-
def create(self):
"""
Instead of the typical "create" view, this displays a "wizard"
@@ -206,7 +185,7 @@ class OrderView(MasterView):
"""
enum = self.app.enum
self.creating = True
- self.batch_handler = self.get_batch_handler()
+ self.batch_handler = NewOrderBatchHandler(self.config)
batch = self.get_current_batch()
context = self.get_context_customer(batch)
@@ -244,7 +223,6 @@ class OrderView(MasterView):
try:
result = getattr(self, action)(batch, data)
except Exception as error:
- log.warning("error calling json action for order", exc_info=True)
result = {'error': self.app.render_error(error)}
return self.json_response(result)
@@ -301,49 +279,91 @@ class OrderView(MasterView):
"""
AJAX view for customer autocomplete, when entering new order.
- This invokes one of the following on the
- :attr:`batch_handler`:
-
- * :meth:`~sideshow.batch.neworder.NewOrderBatchHandler.autocomplete_customers_external()`
- * :meth:`~sideshow.batch.neworder.NewOrderBatchHandler.autocomplete_customers_local()`
-
- :returns: List of search results; each should be a dict with
- ``value`` and ``label`` keys.
+ This should invoke a configured handler for the autocomplete
+ behavior, but that is not yet implemented. For now it uses
+ built-in logic only, which queries the
+ :class:`~sideshow.db.model.customers.LocalCustomer` table.
"""
session = self.Session()
term = self.request.GET.get('term', '').strip()
if not term:
return []
+ return self.mock_autocomplete_customers(session, term, user=self.request.user)
- handler = self.get_batch_handler()
- if handler.use_local_customers():
- return handler.autocomplete_customers_local(session, term, user=self.request.user)
- else:
- return handler.autocomplete_customers_external(session, term, user=self.request.user)
+ # TODO: move this to some handler
+ def mock_autocomplete_customers(self, session, term, user=None):
+ """ """
+ import sqlalchemy as sa
+
+ model = self.app.model
+
+ # base query
+ query = session.query(model.LocalCustomer)
+
+ # filter query
+ criteria = [model.LocalCustomer.full_name.ilike(f'%{word}%')
+ for word in term.split()]
+ query = query.filter(sa.and_(*criteria))
+
+ # sort query
+ query = query.order_by(model.LocalCustomer.full_name)
+
+ # get data
+ # TODO: need max_results option
+ customers = query.all()
+
+ # get results
+ def result(customer):
+ return {'value': customer.uuid.hex,
+ 'label': customer.full_name}
+ return [result(c) for c in customers]
def product_autocomplete(self):
"""
AJAX view for product autocomplete, when entering new order.
- This invokes one of the following on the
- :attr:`batch_handler`:
-
- * :meth:`~sideshow.batch.neworder.NewOrderBatchHandler.autocomplete_products_external()`
- * :meth:`~sideshow.batch.neworder.NewOrderBatchHandler.autocomplete_products_local()`
-
- :returns: List of search results; each should be a dict with
- ``value`` and ``label`` keys.
+ This should invoke a configured handler for the autocomplete
+ behavior, but that is not yet implemented. For now it uses
+ built-in logic only, which queries the
+ :class:`~sideshow.db.model.products.LocalProduct` table.
"""
session = self.Session()
term = self.request.GET.get('term', '').strip()
if not term:
return []
+ return self.mock_autocomplete_products(session, term, user=self.request.user)
- handler = self.get_batch_handler()
- if handler.use_local_products():
- return handler.autocomplete_products_local(session, term, user=self.request.user)
- else:
- return handler.autocomplete_products_external(session, term, user=self.request.user)
+ # TODO: move this to some handler
+ def mock_autocomplete_products(self, session, term, user=None):
+ """ """
+ import sqlalchemy as sa
+
+ model = self.app.model
+
+ # base query
+ query = session.query(model.LocalProduct)
+
+ # filter query
+ criteria = []
+ for word in term.split():
+ criteria.append(sa.or_(
+ model.LocalProduct.brand_name.ilike(f'%{word}%'),
+ model.LocalProduct.description.ilike(f'%{word}%')))
+ query = query.filter(sa.and_(*criteria))
+
+ # sort query
+ query = query.order_by(model.LocalProduct.brand_name,
+ model.LocalProduct.description)
+
+ # get data
+ # TODO: need max_results option
+ products = query.all()
+
+ # get results
+ def result(product):
+ return {'value': product.uuid.hex,
+ 'label': product.full_description}
+ return [result(c) for c in products]
def get_pending_product_required_fields(self):
""" """
@@ -511,12 +531,11 @@ class OrderView(MasterView):
if not product_id:
return {'error': "Must specify a product ID"}
- session = self.Session()
use_local = self.batch_handler.use_local_products()
if use_local:
- data = self.batch_handler.get_product_info_local(session, product_id)
+ data = self.get_local_product_info(product_id)
else:
- data = self.batch_handler.get_product_info_external(session, product_id)
+ raise NotImplementedError("TODO: add integration handler")
if 'error' in data:
return data
@@ -552,6 +571,32 @@ class OrderView(MasterView):
return data
+ # TODO: move this to some handler
+ def get_local_product_info(self, product_id):
+ """ """
+ model = self.app.model
+ session = self.Session()
+ product = session.get(model.LocalProduct, product_id)
+ if not product:
+ return {'error': "Product not found"}
+
+ return {
+ 'product_id': product.uuid.hex,
+ 'scancode': product.scancode,
+ 'brand_name': product.brand_name,
+ 'description': product.description,
+ 'size': product.size,
+ 'full_description': product.full_description,
+ 'weighed': product.weighed,
+ 'special_order': product.special_order,
+ 'department_id': product.department_id,
+ 'department_name': product.department_name,
+ 'case_size': product.case_size,
+ 'unit_price_reg': product.unit_price_reg,
+ 'vendor_name': product.vendor_name,
+ 'vendor_item_code': product.vendor_item_code,
+ }
+
def add_item(self, batch, data):
"""
This adds a row to the user's current new order batch.
@@ -680,9 +725,6 @@ class OrderView(MasterView):
'product_brand': row.product_brand,
'product_description': row.product_description,
'product_size': row.product_size,
- 'product_full_description': self.app.make_full_name(row.product_brand,
- row.product_description,
- row.product_size),
'product_weighed': row.product_weighed,
'department_display': row.department_name,
'special_order': row.special_order,
@@ -709,6 +751,15 @@ class OrderView(MasterView):
else:
data['product_id'] = row.product_id
+ # product_full_description
+ if use_local:
+ if row.local_product:
+ data['product_full_description'] = row.local_product.full_description
+ else: # use external
+ pass # TODO
+ if not data.get('product_id') and row.pending_product:
+ data['product_full_description'] = row.pending_product.full_description
+
# vendor_name
if use_local:
if row.local_product:
@@ -861,20 +912,7 @@ class OrderView(MasterView):
""" """
settings = [
- # batches
- {'name': 'wutta.batch.neworder.handler.spec'},
-
- # customers
- {'name': 'sideshow.orders.use_local_customers',
- # nb. this is really a bool but we present as string in config UI
- #'type': bool,
- 'default': 'true'},
-
# products
- {'name': 'sideshow.orders.use_local_products',
- # nb. this is really a bool but we present as string in config UI
- #'type': bool,
- 'default': 'true'},
{'name': 'sideshow.orders.allow_unknown_products',
'type': bool,
'default': True},
@@ -896,10 +934,6 @@ class OrderView(MasterView):
context['pending_product_fields'] = self.PENDING_PRODUCT_ENTRY_FIELDS
- handlers = self.app.get_batch_handler_specs('neworder')
- handlers = [{'spec': spec} for spec in handlers]
- context['batch_handlers'] = handlers
-
return context
@classmethod
@@ -1011,6 +1045,8 @@ class OrderItemView(MasterView):
'department_id',
'department_name',
'special_order',
+ 'order_qty',
+ 'order_uom',
'case_size',
'unit_cost',
'unit_price_reg',
@@ -1018,8 +1054,6 @@ class OrderItemView(MasterView):
'sale_ends',
'unit_price_quoted',
'case_price_quoted',
- 'order_qty',
- 'order_uom',
'discount_percent',
'total_price',
'status_code',
diff --git a/tests/batch/test_neworder.py b/tests/batch/test_neworder.py
index b3fbf4a..719efcb 100644
--- a/tests/batch/test_neworder.py
+++ b/tests/batch/test_neworder.py
@@ -50,34 +50,6 @@ class TestNewOrderBatchHandler(DataTestCase):
config.setdefault('sideshow.orders.allow_unknown_products', 'false')
self.assertFalse(handler.allow_unknown_products())
- def test_autocomplete_customers_external(self):
- handler = self.make_handler()
- self.assertRaises(NotImplementedError, handler.autocomplete_customers_external,
- self.session, 'jack')
-
- def test_autocomplete_cutomers_local(self):
- model = self.app.model
- handler = self.make_handler()
-
- # empty results by default
- self.assertEqual(handler.autocomplete_customers_local(self.session, 'foo'), [])
-
- # add a customer
- customer = model.LocalCustomer(full_name="Chuck Norris")
- self.session.add(customer)
- self.session.flush()
-
- # search for chuck finds chuck
- results = handler.autocomplete_customers_local(self.session, 'chuck')
- self.assertEqual(len(results), 1)
- self.assertEqual(results[0], {
- 'value': customer.uuid.hex,
- 'label': "Chuck Norris",
- })
-
- # search for sally finds nothing
- self.assertEqual(handler.autocomplete_customers_local(self.session, 'sally'), [])
-
def test_set_customer(self):
model = self.app.model
handler = self.make_handler()
@@ -174,83 +146,6 @@ class TestNewOrderBatchHandler(DataTestCase):
self.assertIsNone(batch.phone_number)
self.assertIsNone(batch.email_address)
- def test_autocomplete_products_external(self):
- handler = self.make_handler()
- self.assertRaises(NotImplementedError, handler.autocomplete_products_external,
- self.session, 'cheese')
-
- def test_autocomplete_products_local(self):
- model = self.app.model
- handler = self.make_handler()
-
- # empty results by default
- self.assertEqual(handler.autocomplete_products_local(self.session, 'foo'), [])
-
- # add a product
- product = model.LocalProduct(brand_name="Bragg's", description="Vinegar")
- self.session.add(product)
- self.session.flush()
-
- # search for vinegar finds product
- results = handler.autocomplete_products_local(self.session, 'vinegar')
- self.assertEqual(len(results), 1)
- self.assertEqual(results[0], {
- 'value': product.uuid.hex,
- 'label': "Bragg's Vinegar",
- })
-
- # search for brag finds product
- results = handler.autocomplete_products_local(self.session, 'brag')
- self.assertEqual(len(results), 1)
- self.assertEqual(results[0], {
- 'value': product.uuid.hex,
- 'label': "Bragg's Vinegar",
- })
-
- # search for juice finds nothing
- self.assertEqual(handler.autocomplete_products_local(self.session, 'juice'), [])
-
- def test_get_product_info_external(self):
- handler = self.make_handler()
- self.assertRaises(NotImplementedError, handler.get_product_info_external,
- self.session, '07430500132')
-
- def test_get_product_info_local(self):
- model = self.app.model
- handler = self.make_handler()
-
- user = model.User(username='barney')
- self.session.add(user)
- local = model.LocalProduct(scancode='07430500132',
- brand_name='Bragg',
- description='Vinegar',
- size='32oz',
- case_size=12,
- unit_price_reg=decimal.Decimal('5.99'))
- self.session.add(local)
- self.session.flush()
-
- batch = handler.make_batch(self.session, created_by=user)
- self.session.add(batch)
-
- # typical, for local product
- info = handler.get_product_info_local(self.session, local.uuid.hex)
- self.assertEqual(info['product_id'], local.uuid.hex)
- self.assertEqual(info['scancode'], '07430500132')
- self.assertEqual(info['brand_name'], 'Bragg')
- self.assertEqual(info['description'], 'Vinegar')
- self.assertEqual(info['size'], '32oz')
- self.assertEqual(info['full_description'], 'Bragg Vinegar 32oz')
- self.assertEqual(info['case_size'], 12)
- self.assertEqual(info['unit_price_reg'], decimal.Decimal('5.99'))
-
- # error if no product_id
- self.assertRaises(ValueError, handler.get_product_info_local, self.session, None)
-
- # error if product not found
- mock_uuid = self.app.make_true_uuid()
- self.assertRaises(ValueError, handler.get_product_info_local, self.session, mock_uuid.hex)
-
def test_add_item(self):
model = self.app.model
enum = self.app.enum
@@ -824,8 +719,10 @@ class TestNewOrderBatchHandler(DataTestCase):
self.session.add(row)
self.session.flush()
# STATUS_OK
- row = handler.add_item(batch, {'scancode': '07430500132'}, 1, enum.ORDER_UOM_UNIT)
- self.session.flush()
+ row = handler.make_row(product_id=42, order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
+ handler.add_row(batch, row)
+ self.session.add(row)
+ self.session.commit()
# only 1 effective row
rows = handler.get_effective_rows(batch)
diff --git a/tests/web/test_menus.py b/tests/web/test_menus.py
index ed64232..36fdc56 100644
--- a/tests/web/test_menus.py
+++ b/tests/web/test_menus.py
@@ -9,12 +9,4 @@ class TestSideshowMenuHandler(WebTestCase):
def test_make_menus(self):
handler = mod.SideshowMenuHandler(self.config)
menus = handler.make_menus(self.request)
- titles = [menu['title'] for menu in menus]
- self.assertEqual(titles, [
- 'Orders',
- 'Customers',
- 'Products',
- 'Batches',
- 'Other',
- 'Admin',
- ])
+ self.assertEqual(len(menus), 5)
diff --git a/tests/web/views/test_orders.py b/tests/web/views/test_orders.py
index 98d00cd..ab996f1 100644
--- a/tests/web/views/test_orders.py
+++ b/tests/web/views/test_orders.py
@@ -42,8 +42,6 @@ class TestOrderView(WebTestCase):
def test_create(self):
self.pyramid_config.include('sideshow.web.views')
- self.config.setdefault('wutta.batch.neworder.handler.spec',
- 'sideshow.batch.neworder:NewOrderBatchHandler')
model = self.app.model
enum = self.app.enum
view = self.make_view()
@@ -179,9 +177,7 @@ class TestOrderView(WebTestCase):
def test_customer_autocomplete(self):
model = self.app.model
- handler = self.make_handler()
view = self.make_view()
- view.batch_handler = handler
with patch.object(view, 'Session', return_value=self.session):
@@ -209,16 +205,9 @@ class TestOrderView(WebTestCase):
result = view.customer_autocomplete()
self.assertEqual(result, [])
- # external lookup not implemented by default
- with patch.object(handler, 'use_local_customers', return_value=False):
- with patch.object(self.request, 'GET', new={'term': 'sally'}, create=True):
- self.assertRaises(NotImplementedError, view.customer_autocomplete)
-
def test_product_autocomplete(self):
model = self.app.model
- handler = self.make_handler()
view = self.make_view()
- view.batch_handler = handler
with patch.object(view, 'Session', return_value=self.session):
@@ -255,11 +244,6 @@ class TestOrderView(WebTestCase):
result = view.product_autocomplete()
self.assertEqual(result, [])
- # external lookup not implemented by default
- with patch.object(handler, 'use_local_products', return_value=False):
- with patch.object(self.request, 'GET', new={'term': 'juice'}, create=True):
- self.assertRaises(NotImplementedError, view.product_autocomplete)
-
def test_get_pending_product_required_fields(self):
model = self.app.model
view = self.make_view()
@@ -545,27 +529,20 @@ class TestOrderView(WebTestCase):
self.assertEqual(context['case_size'], 12)
self.assertEqual(context['unit_price_reg'], 5.99)
+ # error if local product missing
+ mock_uuid = self.app.make_true_uuid()
+ context = view.get_product_info(batch, {'product_id': mock_uuid.hex})
+ self.assertEqual(context, {'error': "Product not found"})
+
# error if no product_id
context = view.get_product_info(batch, {})
self.assertEqual(context, {'error': "Must specify a product ID"})
- # error if product not found
- mock_uuid = self.app.make_true_uuid()
- self.assertRaises(ValueError, view.get_product_info,
- batch, {'product_id': mock_uuid.hex})
-
+ # external lookup not implemented (yet)
with patch.object(handler, 'use_local_products', return_value=False):
-
- # external lookup not implemented by default
self.assertRaises(NotImplementedError, view.get_product_info,
batch, {'product_id': '42'})
- # external lookup may return its own error
- with patch.object(handler, 'get_product_info_external',
- return_value={'error': "something smells fishy"}):
- context = view.get_product_info(batch, {'product_id': '42'})
- self.assertEqual(context, {'error': "something smells fishy"})
-
def test_add_item(self):
model = self.app.model
enum = self.app.enum
@@ -993,47 +970,33 @@ class TestOrderView(WebTestCase):
# the next few tests will morph 2nd row..
- def refresh_external(row):
- row.product_scancode = '012345'
- row.product_brand = 'Acme'
- row.product_description = 'Bricks'
- row.product_size = '1 ton'
- row.product_weighed = True
- row.department_id = 1
- row.department_name = "Bricks & Mortar"
- row.special_order = False
- row.case_size = None
- row.unit_cost = decimal.Decimal('599.99')
- row.unit_price_reg = decimal.Decimal('999.99')
-
# typical, external product
+ row2.product_id = '42'
with patch.object(handler, 'use_local_products', return_value=False):
- with patch.object(handler, 'refresh_row_from_external_product', new=refresh_external):
- handler.update_item(row2, '42', 1, enum.ORDER_UOM_UNIT)
- data = view.normalize_row(row2)
+ data = view.normalize_row(row2)
self.assertEqual(data['uuid'], row2.uuid.hex)
self.assertEqual(data['sequence'], 2)
self.assertEqual(data['product_id'], '42')
- self.assertEqual(data['product_scancode'], '012345')
- self.assertEqual(data['product_full_description'], 'Acme Bricks 1 ton')
+ self.assertIsNone(data['product_scancode'])
+ self.assertNotIn('product_full_description', data) # TODO
self.assertIsNone(data['case_size'])
self.assertNotIn('vendor_name', data) # TODO
self.assertEqual(data['order_qty'], 1)
self.assertEqual(data['order_uom'], 'EA')
self.assertEqual(data['order_qty_display'], '1 Units')
- self.assertEqual(data['unit_price_reg'], 999.99)
- self.assertEqual(data['unit_price_reg_display'], '$999.99')
+ self.assertEqual(data['unit_price_reg'], 3.29)
+ self.assertEqual(data['unit_price_reg_display'], '$3.29')
self.assertNotIn('unit_price_sale', data)
self.assertNotIn('unit_price_sale_display', data)
self.assertNotIn('sale_ends', data)
self.assertNotIn('sale_ends_display', data)
- self.assertEqual(data['unit_price_quoted'], 999.99)
- self.assertEqual(data['unit_price_quoted_display'], '$999.99')
+ self.assertEqual(data['unit_price_quoted'], 3.29)
+ self.assertEqual(data['unit_price_quoted_display'], '$3.29')
self.assertIsNone(data['case_price_quoted'])
self.assertEqual(data['case_price_quoted_display'], '')
- self.assertEqual(data['total_price'], 999.99)
- self.assertEqual(data['total_price_display'], '$999.99')
- self.assertFalse(data['special_order'])
+ self.assertEqual(data['total_price'], 3.29)
+ self.assertEqual(data['total_price_display'], '$3.29')
+ self.assertIsNone(data['special_order'])
self.assertEqual(data['status_code'], row2.STATUS_OK)
self.assertNotIn('pending_product', data)