feat: add config option to show/hide Store ID; default value
This commit is contained in:
		
							parent
							
								
									3ef84ff706
								
							
						
					
					
						commit
						89e3445ace
					
				
					 19 changed files with 445 additions and 64 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue