feat: add basic support to "resolve" a pending product

This commit is contained in:
Lance Edgar 2025-02-21 18:13:57 -06:00
parent 6b4bc3da10
commit 1ee398e8fb
17 changed files with 780 additions and 69 deletions

View file

@ -1219,7 +1219,7 @@ class TestNewOrderBatchHandler(DataTestCase):
self.assertEqual(local.full_name, "Chuck Norris")
self.assertEqual(local.phone_number, '555-1234')
def test_make_local_products(self):
def test_process_pending_products(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
@ -1253,7 +1253,7 @@ class TestNewOrderBatchHandler(DataTestCase):
self.assertEqual(self.session.query(model.LocalProduct).count(), 1)
self.assertIsNotNone(row2.pending_product)
self.assertIsNone(row2.local_product)
handler.make_local_products(batch, batch.rows)
handler.process_pending_products(batch, batch.rows)
self.assertEqual(self.session.query(model.PendingProduct).count(), 0)
self.assertEqual(self.session.query(model.LocalProduct).count(), 2)
self.assertIsNone(row2.pending_product)
@ -1266,7 +1266,7 @@ class TestNewOrderBatchHandler(DataTestCase):
self.assertEqual(local.unit_price_reg, decimal.Decimal('5.99'))
# trying again does nothing
handler.make_local_products(batch, batch.rows)
handler.process_pending_products(batch, batch.rows)
self.assertEqual(self.session.query(model.PendingProduct).count(), 0)
self.assertEqual(self.session.query(model.LocalProduct).count(), 2)
self.assertIsNone(row2.pending_product)
@ -1291,24 +1291,26 @@ class TestNewOrderBatchHandler(DataTestCase):
), 1, enum.ORDER_UOM_UNIT)
self.session.flush()
# should do nothing if local products disabled
# should update status if using external products
with patch.object(handler, 'use_local_products', return_value=False):
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
self.assertEqual(self.session.query(model.LocalProduct).count(), 2)
self.assertIsNotNone(row.pending_product)
self.assertEqual(row.pending_product.status, enum.PendingProductStatus.PENDING)
self.assertIsNone(row.local_product)
handler.make_local_products(batch, batch.rows)
handler.process_pending_products(batch, batch.rows)
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
self.assertEqual(self.session.query(model.LocalProduct).count(), 2)
self.assertIsNotNone(row.pending_product)
self.assertEqual(row.pending_product.status, enum.PendingProductStatus.READY)
self.assertIsNone(row.local_product)
# but things happen by default, since local products enabled
# but if using local products (the default), pending is converted to local
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
self.assertEqual(self.session.query(model.LocalProduct).count(), 2)
self.assertIsNotNone(row.pending_product)
self.assertIsNone(row.local_product)
handler.make_local_products(batch, batch.rows)
handler.process_pending_products(batch, batch.rows)
self.assertEqual(self.session.query(model.PendingProduct).count(), 0)
self.assertEqual(self.session.query(model.LocalProduct).count(), 3)
self.assertIsNone(row.pending_product)

View file

@ -70,6 +70,75 @@ class TestOrderHandler(DataTestCase):
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_EXPIRED), 'warning')
self.assertEqual(handler.item_status_to_variant(enum.ORDER_ITEM_STATUS_INACTIVE), 'warning')
def test_resolve_pending_product(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
# sample data
user = model.User(username='barney')
self.session.add(user)
pending = model.PendingProduct(description='vinegar', unit_price_reg=5.99,
status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(pending)
order = model.Order(order_id=100, customer_name="Fred Flintstone", created_by=user)
item = model.OrderItem(pending_product=pending,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_READY)
order.items.append(item)
self.session.add(order)
self.session.flush()
info = {
'product_id': '07430500132',
'scancode': '07430500132',
'brand_name': "Bragg's",
'description': "Apple Cider Vinegar",
'size': "32oz",
'weighed': False,
'department_id': None,
'department_name': None,
'special_order': False,
'vendor_name': None,
'vendor_item_code': None,
'case_size': 12,
'unit_cost': 2.99,
'unit_price_reg': 5.99,
}
# first try fails b/c pending status
self.assertEqual(len(item.events), 0)
self.assertRaises(ValueError, handler.resolve_pending_product, pending, info, user)
# resolves okay if ready status
pending.status = enum.PendingProductStatus.READY
handler.resolve_pending_product(pending, info, user)
self.assertEqual(len(item.events), 1)
self.assertEqual(item.events[0].type_code, enum.ORDER_ITEM_EVENT_PRODUCT_RESOLVED)
self.assertIsNone(item.events[0].note)
# more sample data
pending2 = model.PendingProduct(description='vinegar', unit_price_reg=5.99,
status=enum.PendingProductStatus.READY,
created_by=user)
self.session.add(pending2)
order2 = model.Order(order_id=101, customer_name="Wilma Flintstone", created_by=user)
item2 = model.OrderItem(pending_product=pending2,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_READY)
order2.items.append(item2)
self.session.add(order2)
self.session.flush()
# resolve with extra note
handler.resolve_pending_product(pending2, info, user, note='hello world')
self.assertEqual(len(item2.events), 2)
self.assertEqual(item2.events[0].type_code, enum.ORDER_ITEM_EVENT_PRODUCT_RESOLVED)
self.assertIsNone(item2.events[0].note)
self.assertEqual(item2.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED)
self.assertEqual(item2.events[1].note, "hello world")
def test_process_placement(self):
model = self.app.model
enum = self.app.enum

View file

@ -1144,6 +1144,8 @@ class TestOrderView(WebTestCase):
row.department_id = 1
row.department_name = "Bricks & Mortar"
row.special_order = False
row.vendor_name = 'Acme Distributors'
row.vendor_item_code = '1234'
row.case_size = None
row.unit_cost = decimal.Decimal('599.99')
row.unit_price_reg = decimal.Decimal('999.99')
@ -1159,7 +1161,8 @@ class TestOrderView(WebTestCase):
self.assertEqual(data['product_scancode'], '012345')
self.assertEqual(data['product_full_description'], 'Acme Bricks 1 ton')
self.assertIsNone(data['case_size'])
self.assertNotIn('vendor_name', data) # TODO
self.assertEqual(data['vendor_name'], 'Acme Distributors')
self.assertEqual(data['vendor_item_code'], '1234')
self.assertEqual(data['order_qty'], 1)
self.assertEqual(data['order_uom'], 'EA')
self.assertEqual(data['order_qty_display'], '1 Units')

View file

@ -132,6 +132,19 @@ class TestPendingProductView(WebTestCase):
self.assertIn('brand_name', grid.linked_columns)
self.assertIn('description', grid.linked_columns)
def test_grid_row_class(self):
enum = self.app.enum
model = self.app.model
view = self.make_view()
product = model.PendingProduct()
# null by default
self.assertIsNone(view.grid_row_class(product, {}, 1))
# warning for ignored
product.status = enum.PendingProductStatus.IGNORED
self.assertEqual(view.grid_row_class(product, {}, 1), 'has-background-warning')
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
@ -141,7 +154,6 @@ class TestPendingProductView(WebTestCase):
with patch.object(view, 'creating', new=True):
form = view.make_form(model_class=model.PendingProduct)
view.configure_form(form)
self.assertNotIn('status', form)
self.assertNotIn('created', form)
self.assertNotIn('created_by', form)
@ -216,6 +228,39 @@ class TestPendingProductView(WebTestCase):
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_get_template_context(self):
enum = self.app.enum
model = self.app.model
view = self.make_view()
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING)
orig_context = {'instance': product}
# local setting omitted by default
context = view.get_template_context(orig_context)
self.assertNotIn('use_local_products', context)
# still omitted even though 'viewing'
with patch.object(view, 'viewing', new=True):
context = view.get_template_context(orig_context)
self.assertNotIn('use_local_products', context)
# still omitted even though correct status
product.status = enum.PendingProductStatus.READY
context = view.get_template_context(orig_context)
self.assertNotIn('use_local_products', context)
# no longer omitted if user has perm
with patch.object(self.request, 'is_root', new=True):
context = view.get_template_context(orig_context)
self.assertIn('use_local_products', context)
# nb. true by default
self.assertTrue(context['use_local_products'])
# accurately reflects config
self.config.setdefault('sideshow.orders.use_local_products', 'false')
context = view.get_template_context(orig_context)
self.assertFalse(context['use_local_products'])
def test_delete_instance(self):
self.pyramid_config.add_route('pending_products.view', '/pending/products/{uuid}')
model = self.app.model
@ -259,3 +304,117 @@ class TestPendingProductView(WebTestCase):
view.delete_instance(product)
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 0)
def test_resolve(self):
self.pyramid_config.add_route('pending_products.view', '/pending/products/{uuid}')
model = self.app.model
enum = self.app.enum
view = self.make_view()
# sample data
user = model.User(username='barney')
self.session.add(user)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
self.session.flush()
info = {
'product_id': '07430500132',
'scancode': '07430500132',
'brand_name': "Bragg's",
'description': "Apple Cider Vinegar",
'size': "32oz",
'weighed': False,
'department_id': None,
'department_name': None,
'special_order': False,
'vendor_name': None,
'vendor_item_code': None,
'case_size': 12,
'unit_cost': 2.99,
'unit_price_reg': 5.99,
}
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
with patch.object(self.request, 'matchdict', new={'uuid': product.uuid}):
# flash error if wrong status
result = view.resolve()
self.assertIsInstance(result, HTTPFound)
self.assertTrue(self.request.session.peek_flash('error'))
self.assertEqual(self.request.session.pop_flash('error'),
["pending product does not have 'ready' status!"])
# flash error if product_id not specified
product.status = enum.PendingProductStatus.READY
result = view.resolve()
self.assertIsInstance(result, HTTPFound)
self.assertTrue(self.request.session.peek_flash('error'))
self.assertEqual(self.request.session.pop_flash('error'),
["must specify valid product_id"])
# more sample data
order = model.Order(order_id=100, created_by=user,
customer_name="Fred Flintstone")
item = model.OrderItem(pending_product=product,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_READY)
order.items.append(item)
self.session.add(order)
# product + order items updated
self.assertIsNone(product.product_id)
self.assertEqual(product.status, enum.PendingProductStatus.READY)
self.assertIsNone(item.product_id)
batch_handler = NewOrderBatchHandler(self.config)
with patch.object(batch_handler, 'get_product_info_external',
return_value=info):
with patch.object(self.app, 'get_batch_handler',
return_value=batch_handler):
with patch.object(self.request, 'POST',
new={'product_id': '07430500132'}):
with patch.object(batch_handler, 'get_product_info_external',
return_value=info):
result = view.resolve()
self.assertIsInstance(result, HTTPFound)
self.assertFalse(self.request.session.peek_flash('error'))
self.assertEqual(product.product_id, '07430500132')
self.assertEqual(product.status, enum.PendingProductStatus.RESOLVED)
self.assertEqual(item.product_id, '07430500132')
def test_ignore(self):
self.pyramid_config.add_route('pending_products.view', '/pending/products/{uuid}')
model = self.app.model
enum = self.app.enum
view = self.make_view()
# sample data
user = model.User(username='barney')
self.session.add(user)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
with patch.object(self.request, 'matchdict', new={'uuid': product.uuid}):
# flash error if wrong status
result = view.ignore()
self.assertIsInstance(result, HTTPFound)
self.assertTrue(self.request.session.peek_flash('error'))
self.assertEqual(self.request.session.pop_flash('error'),
["pending product does not have 'ready' status!"])
# product updated
product.status = enum.PendingProductStatus.READY
self.assertIsNone(product.product_id)
self.assertEqual(product.status, enum.PendingProductStatus.READY)
result = view.ignore()
self.assertIsInstance(result, HTTPFound)
self.assertFalse(self.request.session.peek_flash('error'))
self.assertIsNone(product.product_id)
self.assertEqual(product.status, enum.PendingProductStatus.IGNORED)