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
|
@ -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…
Reference in a new issue