feat: add config option to show/hide Store ID; default value
This commit is contained in:
parent
3ef84ff706
commit
89e3445ace
6
docs/api/sideshow.app.rst
Normal file
6
docs/api/sideshow.app.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``sideshow.app``
|
||||
================
|
||||
|
||||
.. automodule:: sideshow.app
|
||||
:members:
|
|
@ -30,6 +30,7 @@ For an online demo see https://demo.wuttaproject.org/
|
|||
:caption: Package API:
|
||||
|
||||
api/sideshow
|
||||
api/sideshow.app
|
||||
api/sideshow.batch
|
||||
api/sideshow.batch.neworder
|
||||
api/sideshow.cli
|
||||
|
|
|
@ -51,6 +51,9 @@ sideshow_libcache = "sideshow.web.static:libcache"
|
|||
[project.entry-points."paste.app_factory"]
|
||||
"main" = "sideshow.web.app:main"
|
||||
|
||||
[project.entry-points."wutta.app.providers"]
|
||||
sideshow = "sideshow.app:SideshowAppProvider"
|
||||
|
||||
[project.entry-points."wutta.batch.neworder"]
|
||||
"sideshow" = "sideshow.batch.neworder:NewOrderBatchHandler"
|
||||
|
||||
|
|
56
src/sideshow/app.py
Normal file
56
src/sideshow/app.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Sideshow app provider
|
||||
"""
|
||||
|
||||
from wuttjamaican import app as base
|
||||
|
||||
|
||||
class SideshowAppProvider(base.AppProvider):
|
||||
"""
|
||||
The :term:`app provider` for Sideshow.
|
||||
|
||||
This adds the :meth:`get_order_handler()` method to the :term:`app
|
||||
handler`.
|
||||
"""
|
||||
|
||||
def get_order_handler(self, **kwargs):
|
||||
"""
|
||||
Get the configured :term:`order handler` for the app.
|
||||
|
||||
You can specify a custom handler in your :term:`config file`
|
||||
like:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[sideshow]
|
||||
orders.handler_spec = poser.orders:PoserOrderHandler
|
||||
|
||||
:returns: Instance of :class:`~sideshow.orders.OrderHandler`.
|
||||
"""
|
||||
if 'order_handler' not in self.__dict__:
|
||||
spec = self.config.get('sideshow.orders.handler_spec',
|
||||
default='sideshow.orders:OrderHandler')
|
||||
self.order_handler = self.app.load_object(spec)(self.config)
|
||||
return self.order_handler
|
|
@ -50,6 +50,14 @@ class NewOrderBatchHandler(BatchHandler):
|
|||
"""
|
||||
model_class = NewOrderBatch
|
||||
|
||||
def get_default_store_id(self):
|
||||
"""
|
||||
Returns the configured default value for
|
||||
:attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.store_id`,
|
||||
or ``None``.
|
||||
"""
|
||||
return self.config.get('sideshow.orders.default_store_id')
|
||||
|
||||
def use_local_customers(self):
|
||||
"""
|
||||
Returns boolean indicating whether :term:`local customer`
|
||||
|
@ -165,6 +173,18 @@ class NewOrderBatchHandler(BatchHandler):
|
|||
'label': customer.full_name}
|
||||
return [result(c) for c in customers]
|
||||
|
||||
def init_batch(self, batch, session=None, progress=None, **kwargs):
|
||||
"""
|
||||
Initialize a new batch.
|
||||
|
||||
This sets the
|
||||
:attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.store_id`,
|
||||
if the batch does not yet have one and a default is
|
||||
configured.
|
||||
"""
|
||||
if not batch.store_id:
|
||||
batch.store_id = self.get_default_store_id()
|
||||
|
||||
def set_customer(self, batch, customer_info, user=None):
|
||||
"""
|
||||
Set/update customer info for the batch.
|
||||
|
|
|
@ -62,6 +62,15 @@ class Order(model.Base):
|
|||
ID of the store to which the order pertains, if applicable.
|
||||
""")
|
||||
|
||||
store = orm.relationship(
|
||||
'Store',
|
||||
primaryjoin='Store.store_id == Order.store_id',
|
||||
foreign_keys='Order.store_id',
|
||||
doc="""
|
||||
Reference to the :class:`~sideshow.db.model.stores.Store`
|
||||
record, if applicable.
|
||||
""")
|
||||
|
||||
customer_id = sa.Column(sa.String(length=20), nullable=True, doc="""
|
||||
Proper account ID for the :term:`external customer` to which the
|
||||
order pertains, if applicable.
|
||||
|
|
|
@ -51,4 +51,12 @@ class Store(model.Base):
|
|||
""")
|
||||
|
||||
def __str__(self):
|
||||
return self.name or ""
|
||||
return self.get_display()
|
||||
|
||||
def get_display(self):
|
||||
"""
|
||||
Returns the display string for the store, e.g. "001 Acme Goods".
|
||||
"""
|
||||
return ' '.join([(self.store_id or '').strip(),
|
||||
(self.name or '').strip()])\
|
||||
.strip()
|
||||
|
|
|
@ -37,6 +37,14 @@ class OrderHandler(GenericHandler):
|
|||
handler is responsible for creation logic.)
|
||||
"""
|
||||
|
||||
def expose_store_id(self):
|
||||
"""
|
||||
Returns boolean indicating whether the ``store_id`` field
|
||||
should be exposed at all. This is false by default.
|
||||
"""
|
||||
return self.config.get_bool('sideshow.orders.expose_store_id',
|
||||
default=False)
|
||||
|
||||
def get_order_qty_uom_text(self, order_qty, order_uom, case_size=None, html=False):
|
||||
"""
|
||||
Return the display text for a given order quantity.
|
||||
|
|
|
@ -29,6 +29,17 @@
|
|||
<b-field horizontal label="ID">
|
||||
<span>${h.link_to(f"Order ID {order.order_id}", url('orders.view', uuid=order.uuid))} — Item #${item.sequence}</span>
|
||||
</b-field>
|
||||
% if expose_store_id:
|
||||
<b-field horizontal label="Store">
|
||||
<span>
|
||||
% if order.store:
|
||||
${h.link_to(order.store.get_display(), url('stores.view', uuid=order.store.uuid))}
|
||||
% elif order.store_id:
|
||||
${order.store_id}
|
||||
% endif
|
||||
</span>
|
||||
</b-field>
|
||||
% endif
|
||||
<b-field horizontal label="Order Qty">
|
||||
<span>${order_qty_uom_text|n}</span>
|
||||
</b-field>
|
||||
|
|
|
@ -3,6 +3,28 @@
|
|||
|
||||
<%def name="form_content()">
|
||||
|
||||
<h3 class="block is-size-3">Stores</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
|
||||
<b-field>
|
||||
<b-checkbox name="sideshow.orders.expose_store_id"
|
||||
v-model="simpleSettings['sideshow.orders.expose_store_id']"
|
||||
native-value="true"
|
||||
@input="settingsNeedSaved = true">
|
||||
Show/choose the Store ID for each order
|
||||
</b-checkbox>
|
||||
</b-field>
|
||||
|
||||
<b-field v-show="simpleSettings['sideshow.orders.expose_store_id']"
|
||||
label="Default Store ID">
|
||||
<b-input name="sideshow.orders.default_store_id"
|
||||
v-model="simpleSettings['sideshow.orders.default_store_id']"
|
||||
@input="settingsNeedSaved = true"
|
||||
style="width: 25rem;" />
|
||||
</b-field>
|
||||
|
||||
</div>
|
||||
|
||||
<h3 class="block is-size-3">Customers</h3>
|
||||
<div class="block" style="padding-left: 2rem;">
|
||||
|
||||
|
@ -14,6 +36,7 @@
|
|||
<option value="false">External Customers (e.g. in POS)</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
|
||||
</div>
|
||||
|
||||
<h3 class="block is-size-3">Products</h3>
|
||||
|
|
|
@ -42,7 +42,25 @@
|
|||
<script type="text/x-template" id="order-creator-template">
|
||||
<div>
|
||||
|
||||
${self.order_form_buttons()}
|
||||
<div style="display: flex; justify-content: space-between; margin-bottom: 1.5rem;">
|
||||
<div>
|
||||
% if expose_store_id:
|
||||
<b-loading v-model="storeLoading" is-full-page />
|
||||
<b-field label="Store" horizontal
|
||||
:type="storeID ? null : 'is-danger'">
|
||||
<b-select v-model="storeID"
|
||||
@input="storeChanged">
|
||||
<option v-for="store in stores"
|
||||
:key="store.store_id"
|
||||
:value="store.store_id">
|
||||
{{ store.display }}
|
||||
</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
% endif
|
||||
</div>
|
||||
${self.order_form_buttons()}
|
||||
</div>
|
||||
|
||||
<${b}-collapse class="panel"
|
||||
:class="customerPanelType"
|
||||
|
@ -837,6 +855,12 @@
|
|||
|
||||
batchTotalPriceDisplay: ${json.dumps(normalized_batch['total_price_display'])|n},
|
||||
|
||||
% if expose_store_id:
|
||||
stores: ${json.dumps(stores)|n},
|
||||
storeID: ${json.dumps(batch.store_id)|n},
|
||||
storeLoading: false,
|
||||
% endif
|
||||
|
||||
customerPanelOpen: false,
|
||||
customerLoading: false,
|
||||
customerIsKnown: ${json.dumps(customer_is_known)|n},
|
||||
|
@ -1160,6 +1184,28 @@
|
|||
})
|
||||
},
|
||||
|
||||
% if expose_store_id:
|
||||
|
||||
storeChanged(storeID) {
|
||||
this.storeLoading = true
|
||||
const params = {
|
||||
action: 'set_store',
|
||||
store_id: storeID,
|
||||
}
|
||||
this.submitBatchData(params, ({data}) => {
|
||||
this.storeLoading = false
|
||||
}, response => {
|
||||
this.$buefy.toast.open({
|
||||
message: "Update failed: " + (response.data.error || "(unknown error)"),
|
||||
type: 'is-danger',
|
||||
duration: 2000, // 2 seconds
|
||||
})
|
||||
this.storeLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
% endif
|
||||
|
||||
customerChanged(customerID, callback) {
|
||||
this.customerLoading = true
|
||||
|
||||
|
|
|
@ -126,6 +126,10 @@ class NewOrderBatchView(BatchMasterView):
|
|||
'status_code',
|
||||
]
|
||||
|
||||
def __init__(self, request, context=None):
|
||||
super().__init__(request, context=context)
|
||||
self.order_handler = self.app.get_order_handler()
|
||||
|
||||
def get_batch_handler(self):
|
||||
""" """
|
||||
# TODO: call self.app.get_batch_handler()
|
||||
|
@ -135,6 +139,10 @@ class NewOrderBatchView(BatchMasterView):
|
|||
""" """
|
||||
super().configure_grid(g)
|
||||
|
||||
# store_id
|
||||
if not self.order_handler.expose_store_id():
|
||||
g.remove('store_id')
|
||||
|
||||
# total_price
|
||||
g.set_renderer('total_price', 'currency')
|
||||
|
||||
|
@ -142,6 +150,10 @@ class NewOrderBatchView(BatchMasterView):
|
|||
""" """
|
||||
super().configure_form(f)
|
||||
|
||||
# store_id
|
||||
if not self.order_handler.expose_store_id():
|
||||
f.remove('store_id')
|
||||
|
||||
# local_customer
|
||||
f.set_node('local_customer', LocalCustomerRef(self.request))
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ from wuttaweb.views import MasterView
|
|||
from wuttaweb.forms.schema import UserRef, WuttaMoney, WuttaQuantity, WuttaEnum, WuttaDictEnum
|
||||
|
||||
from sideshow.db.model import Order, OrderItem
|
||||
from sideshow.orders import OrderHandler
|
||||
from sideshow.batch.neworder import NewOrderBatchHandler
|
||||
from sideshow.web.forms.schema import (OrderRef,
|
||||
LocalCustomerRef, LocalProductRef,
|
||||
|
@ -155,20 +154,7 @@ class OrderView(MasterView):
|
|||
|
||||
def __init__(self, request, context=None):
|
||||
super().__init__(request, context=context)
|
||||
self.order_handler = self.get_order_handler()
|
||||
|
||||
def get_order_handler(self):
|
||||
"""
|
||||
Returns the configured :term:`order handler`.
|
||||
|
||||
You normally would not need to call this, and can use
|
||||
:attr:`order_handler` instead.
|
||||
|
||||
:rtype: :class:`~sideshow.orders.OrderHandler`
|
||||
"""
|
||||
if hasattr(self, 'order_handler'):
|
||||
return self.order_handler
|
||||
return OrderHandler(self.config)
|
||||
self.order_handler = self.app.get_order_handler()
|
||||
|
||||
def get_batch_handler(self):
|
||||
"""
|
||||
|
@ -190,6 +176,10 @@ class OrderView(MasterView):
|
|||
""" """
|
||||
super().configure_grid(g)
|
||||
|
||||
# store_id
|
||||
if not self.order_handler.expose_store_id():
|
||||
g.remove('store_id')
|
||||
|
||||
# order_id
|
||||
g.set_link('order_id')
|
||||
|
||||
|
@ -223,6 +213,7 @@ class OrderView(MasterView):
|
|||
|
||||
* :meth:`start_over()`
|
||||
* :meth:`cancel_order()`
|
||||
* :meth:`set_store()`
|
||||
* :meth:`assign_customer()`
|
||||
* :meth:`unassign_customer()`
|
||||
* :meth:`set_pending_customer()`
|
||||
|
@ -232,10 +223,12 @@ class OrderView(MasterView):
|
|||
* :meth:`delete_item()`
|
||||
* :meth:`submit_order()`
|
||||
"""
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
self.creating = True
|
||||
session = self.Session()
|
||||
self.batch_handler = self.get_batch_handler()
|
||||
batch = self.get_current_batch()
|
||||
self.creating = True
|
||||
|
||||
context = self.get_context_customer(batch)
|
||||
|
||||
|
@ -254,6 +247,7 @@ class OrderView(MasterView):
|
|||
data = dict(self.request.json_body)
|
||||
action = data.pop('action')
|
||||
json_actions = [
|
||||
'set_store',
|
||||
'assign_customer',
|
||||
'unassign_customer',
|
||||
# 'update_phone_number',
|
||||
|
@ -285,12 +279,25 @@ class OrderView(MasterView):
|
|||
for row in batch.rows],
|
||||
'default_uom_choices': self.get_default_uom_choices(),
|
||||
'default_uom': None, # TODO?
|
||||
'expose_store_id': self.order_handler.expose_store_id(),
|
||||
'allow_item_discounts': self.batch_handler.allow_item_discounts(),
|
||||
'allow_unknown_products': (self.batch_handler.allow_unknown_products()
|
||||
and self.has_perm('create_unknown_product')),
|
||||
'pending_product_required_fields': self.get_pending_product_required_fields(),
|
||||
})
|
||||
|
||||
if context['expose_store_id']:
|
||||
stores = session.query(model.Store)\
|
||||
.filter(model.Store.archived == False)\
|
||||
.order_by(model.Store.store_id)\
|
||||
.all()
|
||||
context['stores'] = [{'store_id': store.store_id, 'display': store.get_display()}
|
||||
for store in stores]
|
||||
|
||||
# set default so things just work
|
||||
if not batch.store_id:
|
||||
batch.store_id = self.batch_handler.get_default_store_id()
|
||||
|
||||
if context['allow_item_discounts']:
|
||||
context['allow_item_discounts_if_on_sale'] = self.batch_handler\
|
||||
.allow_item_discounts_if_on_sale()
|
||||
|
@ -436,9 +443,26 @@ class OrderView(MasterView):
|
|||
url = self.get_index_url()
|
||||
return self.redirect(url)
|
||||
|
||||
def set_store(self, batch, data):
|
||||
"""
|
||||
Assign the
|
||||
:attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.store_id`
|
||||
for a batch.
|
||||
|
||||
This is a "batch action" method which may be called from
|
||||
:meth:`create()`.
|
||||
"""
|
||||
store_id = data.get('store_id')
|
||||
if not store_id:
|
||||
return {'error': "Must provide store_id"}
|
||||
|
||||
batch.store_id = store_id
|
||||
return self.get_context_customer(batch)
|
||||
|
||||
def get_context_customer(self, batch):
|
||||
""" """
|
||||
context = {
|
||||
'store_id': batch.store_id,
|
||||
'customer_is_known': True,
|
||||
'customer_id': None,
|
||||
'customer_name': batch.customer_name,
|
||||
|
@ -810,6 +834,10 @@ class OrderView(MasterView):
|
|||
super().configure_form(f)
|
||||
order = f.model_instance
|
||||
|
||||
# store_id
|
||||
if not self.order_handler.expose_store_id():
|
||||
f.remove('store_id')
|
||||
|
||||
# local_customer
|
||||
if order.customer_id and not order.local_customer:
|
||||
f.remove('local_customer')
|
||||
|
@ -910,8 +938,10 @@ class OrderView(MasterView):
|
|||
""" """
|
||||
settings = [
|
||||
|
||||
# batches
|
||||
{'name': 'wutta.batch.neworder.handler.spec'},
|
||||
# stores
|
||||
{'name': 'sideshow.orders.expose_store_id',
|
||||
'type': bool},
|
||||
{'name': 'sideshow.orders.default_store_id'},
|
||||
|
||||
# customers
|
||||
{'name': 'sideshow.orders.use_local_customers',
|
||||
|
@ -933,6 +963,9 @@ class OrderView(MasterView):
|
|||
{'name': 'sideshow.orders.allow_unknown_products',
|
||||
'type': bool,
|
||||
'default': True},
|
||||
|
||||
# batches
|
||||
{'name': 'wutta.batch.neworder.handler.spec'},
|
||||
]
|
||||
|
||||
# required fields for new product entry
|
||||
|
@ -1037,6 +1070,7 @@ class OrderItemView(MasterView):
|
|||
|
||||
labels = {
|
||||
'order_id': "Order ID",
|
||||
'store_id': "Store ID",
|
||||
'product_id': "Product ID",
|
||||
'product_scancode': "Scancode",
|
||||
'product_brand': "Brand",
|
||||
|
@ -1050,6 +1084,7 @@ class OrderItemView(MasterView):
|
|||
|
||||
grid_columns = [
|
||||
'order_id',
|
||||
'store_id',
|
||||
'customer_name',
|
||||
# 'sequence',
|
||||
'product_scancode',
|
||||
|
@ -1099,20 +1134,7 @@ class OrderItemView(MasterView):
|
|||
|
||||
def __init__(self, request, context=None):
|
||||
super().__init__(request, context=context)
|
||||
self.order_handler = self.get_order_handler()
|
||||
|
||||
def get_order_handler(self):
|
||||
"""
|
||||
Returns the configured :term:`order handler`.
|
||||
|
||||
You normally would not need to call this, and can use
|
||||
:attr:`order_handler` instead.
|
||||
|
||||
:rtype: :class:`~sideshow.orders.OrderHandler`
|
||||
"""
|
||||
if hasattr(self, 'order_handler'):
|
||||
return self.order_handler
|
||||
return OrderHandler(self.config)
|
||||
self.order_handler = self.app.get_order_handler()
|
||||
|
||||
def get_fallback_templates(self, template):
|
||||
""" """
|
||||
|
@ -1132,11 +1154,19 @@ class OrderItemView(MasterView):
|
|||
model = self.app.model
|
||||
# enum = self.app.enum
|
||||
|
||||
# store_id
|
||||
if not self.order_handler.expose_store_id():
|
||||
g.remove('store_id')
|
||||
|
||||
# order_id
|
||||
g.set_sorter('order_id', model.Order.order_id)
|
||||
g.set_renderer('order_id', self.render_order_id)
|
||||
g.set_renderer('order_id', self.render_order_attr)
|
||||
g.set_link('order_id')
|
||||
|
||||
# store_id
|
||||
g.set_sorter('store_id', model.Order.store_id)
|
||||
g.set_renderer('store_id', self.render_order_attr)
|
||||
|
||||
# customer_name
|
||||
g.set_label('customer_name', "Customer", column_only=True)
|
||||
|
||||
|
@ -1165,9 +1195,10 @@ class OrderItemView(MasterView):
|
|||
# status_code
|
||||
g.set_renderer('status_code', self.render_status_code)
|
||||
|
||||
def render_order_id(self, item, key, value):
|
||||
def render_order_attr(self, item, key, value):
|
||||
""" """
|
||||
return item.order.order_id
|
||||
order = item.order
|
||||
return getattr(order, key)
|
||||
|
||||
def render_status_code(self, item, key, value):
|
||||
""" """
|
||||
|
@ -1237,6 +1268,8 @@ class OrderItemView(MasterView):
|
|||
item = context['instance']
|
||||
form = context['form']
|
||||
|
||||
context['expose_store_id'] = self.order_handler.expose_store_id()
|
||||
|
||||
context['item'] = item
|
||||
context['order'] = item.order
|
||||
context['order_qty_uom_text'] = self.order_handler.get_order_qty_uom_text(
|
||||
|
|
|
@ -20,6 +20,16 @@ class TestNewOrderBatchHandler(DataTestCase):
|
|||
def make_handler(self):
|
||||
return mod.NewOrderBatchHandler(self.config)
|
||||
|
||||
def test_get_default_store_id(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# null by default
|
||||
self.assertIsNone(handler.get_default_store_id())
|
||||
|
||||
# whatever is configured
|
||||
self.config.setdefault('sideshow.orders.default_store_id', '042')
|
||||
self.assertEqual(handler.get_default_store_id(), '042')
|
||||
|
||||
def test_use_local_customers(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
|
@ -108,6 +118,23 @@ class TestNewOrderBatchHandler(DataTestCase):
|
|||
# search for sally finds nothing
|
||||
self.assertEqual(handler.autocomplete_customers_local(self.session, 'sally'), [])
|
||||
|
||||
def test_init_batch(self):
|
||||
model = self.app.model
|
||||
handler = self.make_handler()
|
||||
|
||||
# store_id is null by default
|
||||
batch = handler.model_class()
|
||||
self.assertIsNone(batch.store_id)
|
||||
handler.init_batch(batch)
|
||||
self.assertIsNone(batch.store_id)
|
||||
|
||||
# but default can be configured
|
||||
self.config.setdefault('sideshow.orders.default_store_id', '042')
|
||||
batch = handler.model_class()
|
||||
self.assertIsNone(batch.store_id)
|
||||
handler.init_batch(batch)
|
||||
self.assertEqual(batch.store_id, '042')
|
||||
|
||||
def test_set_customer(self):
|
||||
model = self.app.model
|
||||
handler = self.make_handler()
|
||||
|
|
|
@ -13,3 +13,16 @@ class TestPendingCustomer(DataTestCase):
|
|||
|
||||
store.name = "Acme Goods"
|
||||
self.assertEqual(str(store), "Acme Goods")
|
||||
|
||||
store.store_id = "001"
|
||||
self.assertEqual(str(store), "001 Acme Goods")
|
||||
|
||||
def test_get_display(self):
|
||||
store = mod.Store()
|
||||
self.assertEqual(store.get_display(), "")
|
||||
|
||||
store.name = "Acme Goods"
|
||||
self.assertEqual(store.get_display(), "Acme Goods")
|
||||
|
||||
store.store_id = "001"
|
||||
self.assertEqual(store.get_display(), "001 Acme Goods")
|
||||
|
|
17
tests/test_app.py
Normal file
17
tests/test_app.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from wuttjamaican.testing import ConfigTestCase
|
||||
|
||||
from sideshow import app as mod
|
||||
from sideshow.orders import OrderHandler
|
||||
|
||||
|
||||
class TestSideshowAppProvider(ConfigTestCase):
|
||||
|
||||
def make_provider(self):
|
||||
return mod.SideshowAppProvider(self.config)
|
||||
|
||||
def test_get_order_handler(self):
|
||||
provider = self.make_provider()
|
||||
handler = provider.get_order_handler()
|
||||
self.assertIsInstance(handler, OrderHandler)
|
|
@ -16,6 +16,16 @@ class TestOrderHandler(DataTestCase):
|
|||
def make_handler(self):
|
||||
return mod.OrderHandler(self.config)
|
||||
|
||||
def test_expose_store_id(self):
|
||||
handler = self.make_handler()
|
||||
|
||||
# false by default
|
||||
self.assertFalse(handler.expose_store_id())
|
||||
|
||||
# config can enable
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
self.assertTrue(handler.expose_store_id())
|
||||
|
||||
def test_get_order_qty_uom_text(self):
|
||||
enum = self.app.enum
|
||||
handler = self.make_handler()
|
||||
|
|
|
@ -30,10 +30,19 @@ class TestNewOrderBatchView(WebTestCase):
|
|||
def test_configure_grid(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
||||
# store_id not exposed by default
|
||||
grid = view.make_grid(model_class=model.NewOrderBatch)
|
||||
self.assertNotIn('total_price', grid.renderers)
|
||||
self.assertIn('store_id', grid.columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('total_price', grid.renderers)
|
||||
self.assertNotIn('store_id', grid.columns)
|
||||
|
||||
# store_id is exposed if configured
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
grid = view.make_grid(model_class=model.NewOrderBatch)
|
||||
self.assertIn('store_id', grid.columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('store_id', grid.columns)
|
||||
|
||||
def test_configure_form(self):
|
||||
model = self.app.model
|
||||
|
@ -58,6 +67,19 @@ class TestNewOrderBatchView(WebTestCase):
|
|||
self.assertIsInstance(schema['pending_customer'].typ, PendingCustomerRef)
|
||||
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
|
||||
|
||||
# store_id not exposed by default
|
||||
form = view.make_form(model_instance=batch)
|
||||
self.assertIn('store_id', form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('store_id', form)
|
||||
|
||||
# store_id is exposed if configured
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
form = view.make_form(model_instance=batch)
|
||||
self.assertIn('store_id', form)
|
||||
view.configure_form(form)
|
||||
self.assertIn('store_id', form)
|
||||
|
||||
def test_configure_row_grid(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
|
|
|
@ -31,27 +31,28 @@ class TestOrderView(WebTestCase):
|
|||
def make_handler(self):
|
||||
return NewOrderBatchHandler(self.config)
|
||||
|
||||
def test_order_handler(self):
|
||||
view = self.make_view()
|
||||
handler = view.order_handler
|
||||
self.assertIsInstance(handler, OrderHandler)
|
||||
handler2 = view.get_order_handler()
|
||||
self.assertIs(handler2, handler)
|
||||
|
||||
def test_configure_grid(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=model.PendingProduct)
|
||||
self.assertNotIn('order_id', grid.linked_columns)
|
||||
self.assertNotIn('total_price', grid.renderers)
|
||||
|
||||
# store_id hidden by default
|
||||
grid = view.make_grid(model_class=model.Order, columns=['store_id', 'order_id'])
|
||||
self.assertIn('store_id', grid.columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('order_id', grid.linked_columns)
|
||||
self.assertIn('total_price', grid.renderers)
|
||||
self.assertNotIn('store_id', grid.columns)
|
||||
|
||||
# store_id is shown if configured
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
grid = view.make_grid(model_class=model.Order, columns=['store_id', 'order_id'])
|
||||
self.assertIn('store_id', grid.columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('store_id', grid.columns)
|
||||
|
||||
def test_create(self):
|
||||
self.pyramid_config.include('sideshow.web.views')
|
||||
self.config.setdefault('wutta.batch.neworder.handler.spec',
|
||||
'sideshow.batch.neworder:NewOrderBatchHandler')
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
self.config.setdefault('sideshow.orders.allow_item_discounts', 'true')
|
||||
model = self.app.model
|
||||
enum = self.app.enum
|
||||
|
@ -59,6 +60,10 @@ class TestOrderView(WebTestCase):
|
|||
|
||||
user = model.User(username='barney')
|
||||
self.session.add(user)
|
||||
store = model.Store(store_id='001', name='Acme Goods')
|
||||
self.session.add(store)
|
||||
store = model.Store(store_id='002', name='Acme Services')
|
||||
self.session.add(store)
|
||||
self.session.flush()
|
||||
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
|
@ -101,6 +106,7 @@ class TestOrderView(WebTestCase):
|
|||
self.assertIsInstance(response, Response)
|
||||
self.assertEqual(response.content_type, 'application/json')
|
||||
self.assertEqual(response.json_body, {
|
||||
'store_id': None,
|
||||
'customer_is_known': False,
|
||||
'customer_id': None,
|
||||
'customer_name': 'Fred Flintstone',
|
||||
|
@ -304,6 +310,7 @@ class TestOrderView(WebTestCase):
|
|||
self.session.flush()
|
||||
context = view.get_context_customer(batch)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': True,
|
||||
'customer_id': 42,
|
||||
'customer_name': 'Fred Flintstone',
|
||||
|
@ -321,6 +328,7 @@ class TestOrderView(WebTestCase):
|
|||
self.session.flush()
|
||||
context = view.get_context_customer(batch)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': True,
|
||||
'customer_id': local.uuid.hex,
|
||||
'customer_name': 'Betty Boop',
|
||||
|
@ -339,6 +347,7 @@ class TestOrderView(WebTestCase):
|
|||
self.session.flush()
|
||||
context = view.get_context_customer(batch)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': False,
|
||||
'customer_id': None,
|
||||
'customer_name': 'Fred Flintstone',
|
||||
|
@ -357,6 +366,7 @@ class TestOrderView(WebTestCase):
|
|||
self.session.flush()
|
||||
context = view.get_context_customer(batch)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': True, # nb. this is for UI default
|
||||
'customer_id': None,
|
||||
'customer_name': None,
|
||||
|
@ -408,6 +418,34 @@ class TestOrderView(WebTestCase):
|
|||
self.session.flush()
|
||||
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
|
||||
|
||||
def test_set_store(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
handler = NewOrderBatchHandler(self.config)
|
||||
|
||||
user = model.User(username='barney')
|
||||
self.session.add(user)
|
||||
self.session.flush()
|
||||
|
||||
with patch.object(view, 'batch_handler', create=True, new=handler):
|
||||
with patch.object(view, 'Session', return_value=self.session):
|
||||
with patch.object(self.request, 'user', new=user):
|
||||
|
||||
batch = view.get_current_batch()
|
||||
self.assertIsNone(batch.store_id)
|
||||
|
||||
# store_id is required
|
||||
result = view.set_store(batch, {})
|
||||
self.assertEqual(result, {'error': "Must provide store_id"})
|
||||
result = view.set_store(batch, {'store_id': ''})
|
||||
self.assertEqual(result, {'error': "Must provide store_id"})
|
||||
|
||||
# store_id is set on batch
|
||||
result = view.set_store(batch, {'store_id': '042'})
|
||||
self.assertEqual(batch.store_id, '042')
|
||||
self.assertIn('store_id', result)
|
||||
self.assertEqual(result['store_id'], '042')
|
||||
|
||||
def test_assign_customer(self):
|
||||
self.pyramid_config.add_route('orders.create', '/orders/new')
|
||||
model = self.app.model
|
||||
|
@ -432,6 +470,7 @@ class TestOrderView(WebTestCase):
|
|||
self.assertIsNone(batch.pending_customer)
|
||||
self.assertIs(batch.local_customer, weirdal)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': True,
|
||||
'customer_id': weirdal.uuid.hex,
|
||||
'customer_name': 'Weird Al',
|
||||
|
@ -470,6 +509,7 @@ class TestOrderView(WebTestCase):
|
|||
self.assertIsNone(batch.customer_name)
|
||||
self.assertIsNone(batch.local_customer)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': True,
|
||||
'customer_id': None,
|
||||
'customer_name': None,
|
||||
|
@ -510,6 +550,7 @@ class TestOrderView(WebTestCase):
|
|||
context = view.set_pending_customer(batch, data)
|
||||
self.assertIsInstance(batch.pending_customer, model.PendingCustomer)
|
||||
self.assertEqual(context, {
|
||||
'store_id': None,
|
||||
'customer_is_known': False,
|
||||
'customer_id': None,
|
||||
'customer_name': 'Fred Flintstone',
|
||||
|
@ -1078,7 +1119,11 @@ class TestOrderView(WebTestCase):
|
|||
form = view.make_form(model_instance=order)
|
||||
# nb. this is to avoid include/exclude ambiguity
|
||||
form.remove('items')
|
||||
# nb. store_id gets hidden by default
|
||||
form.append('store_id')
|
||||
self.assertIn('store_id', form)
|
||||
view.configure_form(form)
|
||||
self.assertNotIn('store_id', form)
|
||||
schema = form.get_schema()
|
||||
self.assertIn('pending_customer', form)
|
||||
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
|
||||
|
@ -1089,13 +1134,20 @@ class TestOrderView(WebTestCase):
|
|||
self.session.add(local)
|
||||
self.session.flush()
|
||||
|
||||
# nb. from now on we include store_id
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
|
||||
# viewing (local customer)
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
with patch.object(order, 'local_customer', new=local):
|
||||
form = view.make_form(model_instance=order)
|
||||
# nb. this is to avoid include/exclude ambiguity
|
||||
form.remove('items')
|
||||
# nb. store_id will now remain
|
||||
form.append('store_id')
|
||||
self.assertIn('store_id', form)
|
||||
view.configure_form(form)
|
||||
self.assertIn('store_id', form)
|
||||
self.assertNotIn('pending_customer', form)
|
||||
schema = form.get_schema()
|
||||
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
|
||||
|
@ -1272,13 +1324,6 @@ class TestOrderView(WebTestCase):
|
|||
|
||||
class OrderItemViewTestMixin:
|
||||
|
||||
def test_common_order_handler(self):
|
||||
view = self.make_view()
|
||||
handler = view.order_handler
|
||||
self.assertIsInstance(handler, OrderHandler)
|
||||
handler2 = view.get_order_handler()
|
||||
self.assertIs(handler2, handler)
|
||||
|
||||
def test_common_get_fallback_templates(self):
|
||||
view = self.make_view()
|
||||
|
||||
|
@ -1294,18 +1339,29 @@ class OrderItemViewTestMixin:
|
|||
def test_common_configure_grid(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
grid = view.make_grid(model_class=model.OrderItem)
|
||||
self.assertNotIn('order_id', grid.linked_columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('order_id', grid.linked_columns)
|
||||
|
||||
def test_common_render_order_id(self):
|
||||
# store_id is removed by default
|
||||
grid = view.make_grid(model_class=model.OrderItem)
|
||||
grid.append('store_id')
|
||||
self.assertIn('store_id', grid.columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertNotIn('store_id', grid.columns)
|
||||
|
||||
# store_id is shown if configured
|
||||
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
|
||||
grid = view.make_grid(model_class=model.OrderItem)
|
||||
grid.append('store_id')
|
||||
self.assertIn('store_id', grid.columns)
|
||||
view.configure_grid(grid)
|
||||
self.assertIn('store_id', grid.columns)
|
||||
|
||||
def test_common_render_order_attr(self):
|
||||
model = self.app.model
|
||||
view = self.make_view()
|
||||
order = model.Order(order_id=42)
|
||||
item = model.OrderItem()
|
||||
order.items.append(item)
|
||||
self.assertEqual(view.render_order_id(item, None, None), 42)
|
||||
self.assertEqual(view.render_order_attr(item, 'order_id', None), 42)
|
||||
|
||||
def test_common_render_status_code(self):
|
||||
enum = self.app.enum
|
||||
|
|
Loading…
Reference in a new issue