fix: expose config for new order w/ pending product
- whether or not this feature should be allowed at all - user must have permission also - UI should honor both of the above - also, which fields are required for new pending product
This commit is contained in:
		
							parent
							
								
									e677cd5d8c
								
							
						
					
					
						commit
						ebd22fe6ee
					
				
					 5 changed files with 177 additions and 28 deletions
				
			
		|  | @ -44,6 +44,14 @@ class NewOrderBatchHandler(BatchHandler): | |||
|     """ | ||||
|     model_class = NewOrderBatch | ||||
| 
 | ||||
|     def allow_unknown_product(self): | ||||
|         """ | ||||
|         Returns a boolean indicating whether "unknown" (pending) | ||||
|         products are allowed when creating a new order. | ||||
|         """ | ||||
|         return self.config.get_bool('sideshow.orders.allow_unknown_product', | ||||
|                                     default=True) | ||||
| 
 | ||||
|     def set_pending_customer(self, batch, data): | ||||
|         """ | ||||
|         Set (add or update) pending customer info for the batch. | ||||
|  | @ -122,6 +130,9 @@ class NewOrderBatchHandler(BatchHandler): | |||
|            :class:`~sideshow.db.model.batch.neworder.NewOrderBatchRow` | ||||
|            which was added to the batch. | ||||
|         """ | ||||
|         if not self.allow_unknown_product(): | ||||
|             raise TypeError("unknown/pending product not allowed for new orders") | ||||
| 
 | ||||
|         model = self.app.model | ||||
|         enum = self.app.enum | ||||
|         session = self.app.get_session(batch) | ||||
|  | @ -167,6 +178,9 @@ class NewOrderBatchHandler(BatchHandler): | |||
|         :param data: Dict of field data for the | ||||
|            :class:`~sideshow.db.model.products.PendingProduct` record. | ||||
|         """ | ||||
|         if not self.allow_unknown_product(): | ||||
|             raise TypeError("unknown/pending product not allowed for new orders") | ||||
| 
 | ||||
|         model = self.app.model | ||||
|         enum = self.app.enum | ||||
|         session = self.app.get_session(row) | ||||
|  |  | |||
							
								
								
									
										41
									
								
								src/sideshow/web/templates/orders/configure.mako
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/sideshow/web/templates/orders/configure.mako
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | |||
| ## -*- coding: utf-8; -*- | ||||
| <%inherit file="/configure.mako" /> | ||||
| 
 | ||||
| <%def name="form_content()"> | ||||
| 
 | ||||
|   <h3 class="block is-size-3">Products</h3> | ||||
|   <div class="block" style="padding-left: 2rem;"> | ||||
| 
 | ||||
|     <b-field message="If set, user can enter details of an arbitrary new "pending" product."> | ||||
|       <b-checkbox name="sideshow.orders.allow_unknown_product" | ||||
|                   v-model="simpleSettings['sideshow.orders.allow_unknown_product']" | ||||
|                   native-value="true" | ||||
|                   @input="settingsNeedSaved = true"> | ||||
|         Allow creating orders for "unknown" products | ||||
|       </b-checkbox> | ||||
|     </b-field> | ||||
| 
 | ||||
|     <div v-show="simpleSettings['sideshow.orders.allow_unknown_product']" | ||||
|          style="padding-left: 2rem;"> | ||||
| 
 | ||||
|       <p class="block"> | ||||
|         Require these fields for new product: | ||||
|       </p> | ||||
| 
 | ||||
|       <div class="block" | ||||
|            style="margin-left: 2rem;"> | ||||
|         % for field in pending_product_fields: | ||||
|             <b-field> | ||||
|               <b-checkbox name="sideshow.orders.unknown_product.fields.${field}.required" | ||||
|                           v-model="simpleSettings['sideshow.orders.unknown_product.fields.${field}.required']" | ||||
|                           native-value="true" | ||||
|                           @input="settingsNeedSaved = true"> | ||||
|                 ${field} | ||||
|               </b-checkbox> | ||||
|             </b-field> | ||||
|         % endfor | ||||
|       </div> | ||||
| 
 | ||||
|     </div> | ||||
|   </div> | ||||
| </%def> | ||||
|  | @ -402,8 +402,8 @@ | |||
|                           <div v-if="productID"> | ||||
| 
 | ||||
|                             <b-field grouped> | ||||
|                               <b-field :label="productKeyLabel"> | ||||
|                                 <span>{{ productKey }}</span> | ||||
|                               <b-field label="Scancode"> | ||||
|                                 <span>{{ productScancode }}</span> | ||||
|                               </b-field> | ||||
| 
 | ||||
|                               <b-field label="Unit Size"> | ||||
|  | @ -493,12 +493,12 @@ | |||
| 
 | ||||
|                         <div style="display: flex; gap: 1rem;"> | ||||
| 
 | ||||
|                           <b-field :label="productKeyLabel" | ||||
|                                    % if 'key' in pending_product_required_fields: | ||||
|                                    :type="pendingProduct[productKeyField] ? null : 'is-danger'" | ||||
|                           <b-field label="Scancode" | ||||
|                                    % if 'scancode' in pending_product_required_fields: | ||||
|                                    :type="pendingProduct.scancode ? null : 'is-danger'" | ||||
|                                    % endif | ||||
|                                    style="width: 100%;"> | ||||
|                             <b-input v-model="pendingProduct[productKeyField]" /> | ||||
|                             <b-input v-model="pendingProduct.scancode" /> | ||||
|                           </b-field> | ||||
| 
 | ||||
|                           <b-field label="Department" | ||||
|  | @ -725,9 +725,9 @@ | |||
|                      :data="items" | ||||
|                      :row-class="(row, i) => row.product_id ? null : 'has-text-success'"> | ||||
| 
 | ||||
|               <${b}-table-column :label="productKeyLabel" | ||||
|               <${b}-table-column label="Scancode" | ||||
|                               v-slot="props"> | ||||
|                 {{ props.row.product_key }} | ||||
|                 {{ props.row.product_scancode }} | ||||
|               </${b}-table-column> | ||||
| 
 | ||||
|               <${b}-table-column label="Brand" | ||||
|  | @ -876,16 +876,11 @@ | |||
|                     itemDialogTabIndex: 0, | ||||
|                 % endif | ||||
| 
 | ||||
|                 ## TODO | ||||
|                 productIsKnown: false, | ||||
| 
 | ||||
|                 productIsKnown: true, | ||||
|                 selectedProduct: null, | ||||
|                 productID: null, | ||||
|                 productDisplay: null, | ||||
|                 ## TODO | ||||
|                 productKey: null, | ||||
|                 productKeyField: 'scancode', | ||||
|                 productKeyLabel: "Scancode", | ||||
|                 productScancode: null, | ||||
|                 productSize: null, | ||||
|                 productCaseQuantity: null, | ||||
|                 productUnitPrice: null, | ||||
|  | @ -1341,13 +1336,11 @@ | |||
|             showAddItemDialog() { | ||||
|                 this.customerPanelOpen = false | ||||
|                 this.editingItem = null | ||||
|                 // TODO | ||||
|                 // this.productIsKnown = true | ||||
|                 this.productIsKnown = false | ||||
|                 this.productIsKnown = true | ||||
|                 ## this.selectedProduct = null | ||||
|                 this.productID = null | ||||
|                 this.productDisplay = null | ||||
|                 ## this.productKey = null | ||||
|                 this.productScancode = null | ||||
|                 this.productSize = null | ||||
|                 this.productCaseQuantity = null | ||||
|                 this.productUnitPrice = null | ||||
|  | @ -1402,7 +1395,7 @@ | |||
|                 this.pendingProduct = pending | ||||
| 
 | ||||
|                 this.productDisplay = row.product_full_description | ||||
|                 this.productKey = row.product_key | ||||
|                 this.productScancode = row.product_scancode | ||||
|                 this.productSize = row.product_size | ||||
|                 this.productCaseQuantity = row.case_quantity | ||||
|                 this.productURL = row.product_url | ||||
|  |  | |||
|  | @ -58,6 +58,7 @@ class OrderView(MasterView): | |||
|     """ | ||||
|     model_class = Order | ||||
|     editable = False | ||||
|     configurable = True | ||||
| 
 | ||||
|     labels = { | ||||
|         'order_id': "Order ID", | ||||
|  | @ -122,15 +123,14 @@ class OrderView(MasterView): | |||
| 
 | ||||
|     PENDING_PRODUCT_ENTRY_FIELDS = [ | ||||
|         'scancode', | ||||
|         'department_id', | ||||
|         'department_name', | ||||
|         'brand_name', | ||||
|         'description', | ||||
|         'size', | ||||
|         'department_name', | ||||
|         'vendor_name', | ||||
|         'vendor_item_code', | ||||
|         'unit_cost', | ||||
|         'case_size', | ||||
|         'unit_cost', | ||||
|         'unit_price_reg', | ||||
|     ] | ||||
| 
 | ||||
|  | @ -213,10 +213,10 @@ class OrderView(MasterView): | |||
|             'normalized_batch': self.normalize_batch(batch), | ||||
|             'order_items': [self.normalize_row(row) | ||||
|                             for row in batch.rows], | ||||
| 
 | ||||
|             'allow_unknown_product': True, # TODO | ||||
|             'default_uom_choices': self.get_default_uom_choices(), | ||||
|             'default_uom': None, # TODO? | ||||
|             'allow_unknown_product': (self.batch_handler.allow_unknown_product() | ||||
|                                       and self.has_perm('create_unknown_product')), | ||||
|             'pending_product_required_fields': self.get_pending_product_required_fields(), | ||||
|         }) | ||||
|         return self.render_to_response('create', context) | ||||
|  | @ -567,9 +567,6 @@ class OrderView(MasterView): | |||
|                 'special_order': pending.special_order, | ||||
|             } | ||||
| 
 | ||||
|         # TODO: remove this | ||||
|         data['product_key'] = row.product_scancode | ||||
| 
 | ||||
|         # display text for order qty/uom | ||||
|         if row.order_uom == enum.ORDER_UOM_CASE: | ||||
|             if row.case_size is None: | ||||
|  | @ -670,6 +667,55 @@ class OrderView(MasterView): | |||
|         """ """ | ||||
|         return self.request.route_url('order_items.view', uuid=item.uuid) | ||||
| 
 | ||||
|     def configure_get_simple_settings(self): | ||||
|         """ """ | ||||
|         settings = [ | ||||
| 
 | ||||
|             # products | ||||
|             {'name': 'sideshow.orders.allow_unknown_product', | ||||
|              'type': bool, | ||||
|              'default': True}, | ||||
|         ] | ||||
| 
 | ||||
|         # required fields for new product entry | ||||
|         for field in self.PENDING_PRODUCT_ENTRY_FIELDS: | ||||
|             setting = {'name': f'sideshow.orders.unknown_product.fields.{field}.required', | ||||
|                        'type': bool} | ||||
|             if field == 'description': | ||||
|                 setting['default'] = True | ||||
|             settings.append(setting) | ||||
| 
 | ||||
|         return settings | ||||
| 
 | ||||
|     def configure_get_context(self, **kwargs): | ||||
|         """ """ | ||||
|         context = super().configure_get_context(**kwargs) | ||||
| 
 | ||||
|         context['pending_product_fields'] = self.PENDING_PRODUCT_ENTRY_FIELDS | ||||
| 
 | ||||
|         return context | ||||
| 
 | ||||
|     @classmethod | ||||
|     def defaults(cls, config): | ||||
|         cls._order_defaults(config) | ||||
|         cls._defaults(config) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _order_defaults(cls, config): | ||||
|         permission_prefix = cls.get_permission_prefix() | ||||
|         model_title = cls.get_model_title() | ||||
|         model_title_plural = cls.get_model_title_plural() | ||||
| 
 | ||||
|         # fix perm group | ||||
|         config.add_wutta_permission_group(permission_prefix, | ||||
|                                           model_title_plural, | ||||
|                                           overwrite=False) | ||||
| 
 | ||||
|         # extra perm required to create order with unknown/pending product | ||||
|         config.add_wutta_permission(permission_prefix, | ||||
|                                     f'{permission_prefix}.create_unknown_product', | ||||
|                                     f"Create new {model_title} for unknown/pending product") | ||||
| 
 | ||||
| 
 | ||||
| class OrderItemView(MasterView): | ||||
|     """ | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| # -*- coding: utf-8; -*- | ||||
| 
 | ||||
| import datetime | ||||
| import decimal | ||||
| 
 | ||||
| from wuttjamaican.testing import DataTestCase | ||||
|  | @ -18,6 +19,16 @@ class TestNewOrderBatchHandler(DataTestCase): | |||
|     def make_handler(self): | ||||
|         return mod.NewOrderBatchHandler(self.config) | ||||
| 
 | ||||
|     def tets_allow_unknown_product(self): | ||||
|         handler = self.make_handler() | ||||
| 
 | ||||
|         # true by default | ||||
|         self.assertTrue(handler.allow_unknown_product()) | ||||
| 
 | ||||
|         # config can disable | ||||
|         config.setdefault('sideshow.orders.allow_unknown_product', 'false') | ||||
|         self.assertFalse(handler.allow_unknown_product()) | ||||
| 
 | ||||
|     def test_set_pending_customer(self): | ||||
|         model = self.app.model | ||||
|         handler = self.make_handler() | ||||
|  | @ -119,6 +130,10 @@ class TestNewOrderBatchHandler(DataTestCase): | |||
|         self.assertEqual(product.unit_price_reg, decimal.Decimal('5.99')) | ||||
|         self.assertIs(product.created_by, user) | ||||
| 
 | ||||
|         # error if unknown products not allowed | ||||
|         self.config.setdefault('sideshow.orders.allow_unknown_product', 'false') | ||||
|         self.assertRaises(TypeError, handler.add_pending_product, batch, kw, 1, enum.ORDER_UOM_UNIT) | ||||
| 
 | ||||
|     def test_set_pending_product(self): | ||||
|         model = self.app.model | ||||
|         enum = self.app.enum | ||||
|  | @ -210,6 +225,15 @@ class TestNewOrderBatchHandler(DataTestCase): | |||
|         self.assertEqual(product.unit_price_reg, decimal.Decimal('3.59')) | ||||
|         self.assertIs(product.created_by, user) | ||||
| 
 | ||||
|         # error if unknown products not allowed | ||||
|         self.config.setdefault('sideshow.orders.allow_unknown_product', 'false') | ||||
|         self.assertRaises(TypeError, handler.set_pending_product, row, dict( | ||||
|             scancode='07430500116', | ||||
|             size='16oz', | ||||
|             unit_cost=decimal.Decimal('2.19'), | ||||
|             unit_price_reg=decimal.Decimal('3.59'), | ||||
|         )) | ||||
| 
 | ||||
|     def test_refresh_row(self): | ||||
|         model = self.app.model | ||||
|         enum = self.app.enum | ||||
|  | @ -310,6 +334,37 @@ class TestNewOrderBatchHandler(DataTestCase): | |||
|         self.assertEqual(row.case_price_quoted, decimal.Decimal('71.88')) | ||||
|         self.assertEqual(row.total_price, decimal.Decimal('143.76')) | ||||
| 
 | ||||
|         # refreshed from pending product (sale price) | ||||
|         product = model.PendingProduct(scancode='07430500132', | ||||
|                                        brand_name='Bragg', | ||||
|                                        description='Vinegar', | ||||
|                                        size='32oz', | ||||
|                                        case_size=12, | ||||
|                                        unit_cost=decimal.Decimal('3.99'), | ||||
|                                        unit_price_reg=decimal.Decimal('5.99'), | ||||
|                                        created_by=user, | ||||
|                                        status=enum.PendingProductStatus.PENDING) | ||||
|         row = handler.make_row(pending_product=product, order_qty=2, order_uom=enum.ORDER_UOM_CASE, | ||||
|                                unit_price_sale=decimal.Decimal('5.19'), | ||||
|                                sale_ends=datetime.datetime(2099, 1, 1)) | ||||
|         self.assertIsNone(row.status_code) | ||||
|         handler.add_row(batch, row) | ||||
|         self.assertEqual(row.status_code, row.STATUS_OK) | ||||
|         self.assertIsNone(row.product_id) | ||||
|         self.assertIs(row.pending_product, product) | ||||
|         self.assertEqual(row.product_scancode, '07430500132') | ||||
|         self.assertEqual(row.product_brand, 'Bragg') | ||||
|         self.assertEqual(row.product_description, 'Vinegar') | ||||
|         self.assertEqual(row.product_size, '32oz') | ||||
|         self.assertEqual(row.case_size, 12) | ||||
|         self.assertEqual(row.unit_cost, decimal.Decimal('3.99')) | ||||
|         self.assertEqual(row.unit_price_reg, decimal.Decimal('5.99')) | ||||
|         self.assertEqual(row.unit_price_sale, decimal.Decimal('5.19')) | ||||
|         self.assertEqual(row.sale_ends, datetime.datetime(2099, 1, 1)) | ||||
|         self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.19')) | ||||
|         self.assertEqual(row.case_price_quoted, decimal.Decimal('62.28')) | ||||
|         self.assertEqual(row.total_price, decimal.Decimal('124.56')) | ||||
| 
 | ||||
|     def test_remove_row(self): | ||||
|         model = self.app.model | ||||
|         enum = self.app.enum | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar