feat: add basic support for local customer, product lookups

also convert pending to local (where relevant) when executing batch
This commit is contained in:
Lance Edgar 2025-01-09 12:13:58 -06:00
parent ebd22fe6ee
commit a4ad23c7fa
23 changed files with 3205 additions and 796 deletions

View file

@ -16,6 +16,114 @@ class TestIncludeme(WebTestCase):
mod.includeme(self.pyramid_config)
class TestLocalCustomerView(WebTestCase):
def make_view(self):
return mod.LocalCustomerView(self.request)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.LocalCustomer)
self.assertNotIn('full_name', grid.linked_columns)
view.configure_grid(grid)
self.assertIn('full_name', grid.linked_columns)
def test_configure_form(self):
model = self.app.model
view = self.make_view()
# creating
with patch.object(view, 'creating', new=True):
form = view.make_form(model_class=model.LocalCustomer)
view.configure_form(form)
self.assertNotIn('external_id', form)
self.assertNotIn('full_name', form)
self.assertNotIn('orders', form)
self.assertNotIn('new_order_batches', form)
user = model.User(username='barney')
self.session.add(user)
customer = model.LocalCustomer()
self.session.add(customer)
self.session.commit()
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=customer)
view.configure_form(form)
self.assertIn('external_id', form)
self.assertIn('full_name', form)
self.assertIn('orders', form)
self.assertIn('new_order_batches', form)
def test_make_orders_grid(self):
model = self.app.model
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
customer = model.LocalCustomer()
self.session.add(customer)
order = model.Order(order_id=42, local_customer=customer, created_by=user)
self.session.add(order)
self.session.commit()
# no view perm
grid = view.make_orders_grid(customer)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_orders_grid(customer)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_make_new_order_batches_grid(self):
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
customer = model.LocalCustomer()
self.session.add(customer)
batch = handler.make_batch(self.session, local_customer=customer, created_by=user)
self.session.add(batch)
self.session.commit()
# no view perm
grid = view.make_new_order_batches_grid(customer)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_new_order_batches_grid(customer)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_objectify(self):
model = self.app.model
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
with patch.object(view, 'creating', new=True):
with patch.object(self.request, 'user', new=user):
form = view.make_model_form()
with patch.object(form, 'validated', create=True, new={
'first_name': 'Chuck',
'last_name': 'Norris',
}):
customer = view.objectify(form)
self.assertIsInstance(customer, model.LocalCustomer)
self.assertEqual(customer.first_name, 'Chuck')
self.assertEqual(customer.last_name, 'Norris')
self.assertEqual(customer.full_name, 'Chuck Norris')
class TestPendingCustomerView(WebTestCase):
def make_view(self):

View file

@ -13,7 +13,7 @@ from wuttaweb.forms.schema import WuttaMoney
from sideshow.batch.neworder import NewOrderBatchHandler
from sideshow.testing import WebTestCase
from sideshow.web.views import orders as mod
from sideshow.web.forms.schema import OrderRef
from sideshow.web.forms.schema import OrderRef, PendingProductRef
class TestIncludeme(WebTestCase):
@ -27,6 +27,9 @@ class TestOrderView(WebTestCase):
def make_view(self):
return mod.OrderView(self.request)
def make_handler(self):
return NewOrderBatchHandler(self.config)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
@ -40,6 +43,7 @@ class TestOrderView(WebTestCase):
def test_create(self):
self.pyramid_config.include('sideshow.web.views')
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
@ -91,7 +95,7 @@ class TestOrderView(WebTestCase):
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': 'Fred Flintstone',
'new_customer_full_name': 'Fred Flintstone',
'new_customer_first_name': 'Fred',
'new_customer_last_name': 'Flintstone',
'new_customer_phone': '555-1234',
@ -108,6 +112,40 @@ class TestOrderView(WebTestCase):
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json_body, {'error': 'unknown form action'})
# add item
with patch.multiple(self.request, create=True,
method='POST',
json_body={'action': 'add_item',
'product_info': {
'scancode': '07430500132',
'description': 'Vinegar',
'unit_price_reg': 5.99,
},
'order_qty': 1,
'order_uom': enum.ORDER_UOM_UNIT}):
response = view.create()
self.assertIsInstance(response, Response)
self.assertEqual(response.content_type, 'application/json')
data = response.json_body
self.assertEqual(sorted(data), ['batch', 'row'])
# add item, w/ error
with patch.object(NewOrderBatchHandler, 'add_item', side_effect=RuntimeError):
with patch.multiple(self.request, create=True,
method='POST',
json_body={'action': 'add_item',
'product_info': {
'scancode': '07430500116',
'description': 'Vinegar',
'unit_price_reg': 3.59,
},
'order_qty': 1,
'order_uom': enum.ORDER_UOM_UNIT}):
response = view.create()
self.assertIsInstance(response, Response)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json_body, {'error': 'RuntimeError'})
def test_get_current_batch(self):
model = self.app.model
handler = NewOrderBatchHandler(self.config)
@ -137,6 +175,75 @@ class TestOrderView(WebTestCase):
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
self.assertIs(batch2, batch)
def test_customer_autocomplete(self):
model = self.app.model
view = self.make_view()
with patch.object(view, 'Session', return_value=self.session):
# empty results by default
self.assertEqual(view.customer_autocomplete(), [])
with patch.object(self.request, 'GET', new={'term': 'foo'}, create=True):
self.assertEqual(view.customer_autocomplete(), [])
# add a customer
customer = model.LocalCustomer(full_name="Chuck Norris")
self.session.add(customer)
self.session.flush()
# search for chuck finds chuck
with patch.object(self.request, 'GET', new={'term': 'chuck'}, create=True):
result = view.customer_autocomplete()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], {
'value': customer.uuid.hex,
'label': "Chuck Norris",
})
# search for sally finds nothing
with patch.object(self.request, 'GET', new={'term': 'sally'}, create=True):
result = view.customer_autocomplete()
self.assertEqual(result, [])
def test_product_autocomplete(self):
model = self.app.model
view = self.make_view()
with patch.object(view, 'Session', return_value=self.session):
# empty results by default
self.assertEqual(view.product_autocomplete(), [])
with patch.object(self.request, 'GET', new={'term': 'foo'}, create=True):
self.assertEqual(view.product_autocomplete(), [])
# add a product
product = model.LocalProduct(brand_name="Bragg's", description="Vinegar")
self.session.add(product)
self.session.flush()
# search for vinegar finds product
with patch.object(self.request, 'GET', new={'term': 'vinegar'}, create=True):
result = view.product_autocomplete()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], {
'value': product.uuid.hex,
'label': "Bragg's Vinegar",
})
# search for brag finds product
with patch.object(self.request, 'GET', new={'term': 'brag'}, create=True):
result = view.product_autocomplete()
self.assertEqual(len(result), 1)
self.assertEqual(result[0], {
'value': product.uuid.hex,
'label': "Bragg's Vinegar",
})
# search for juice finds nothing
with patch.object(self.request, 'GET', new={'term': 'juice'}, create=True):
result = view.product_autocomplete()
self.assertEqual(result, [])
def test_get_pending_product_required_fields(self):
model = self.app.model
view = self.make_view()
@ -158,38 +265,51 @@ class TestOrderView(WebTestCase):
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
view.batch_handler = handler
user = model.User(username='barney')
self.session.add(user)
# with true customer
# with external customer
with patch.object(handler, 'use_local_customers', return_value=False):
batch = handler.make_batch(self.session, created_by=user,
customer_id=42, customer_name='Fred Flintstone',
phone_number='555-1234', email_address='fred@mailinator.com')
self.session.add(batch)
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'customer_is_known': True,
'customer_id': 42,
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
})
# with local customer
local = model.LocalCustomer(full_name="Betty Boop")
self.session.add(local)
batch = handler.make_batch(self.session, created_by=user,
customer_id=42, customer_name='Fred Flintstone',
phone_number='555-1234', email_address='fred@mailinator.com')
local_customer=local, customer_name='Betty Boop',
phone_number='555-8888')
self.session.add(batch)
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'customer_is_known': True,
'customer_id': 42,
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': None,
'new_customer_first_name': None,
'new_customer_last_name': None,
'new_customer_phone': None,
'new_customer_email': None,
'customer_id': local.uuid.hex,
'customer_name': 'Betty Boop',
'phone_number': '555-8888',
'email_address': None,
})
# with pending customer
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
handler.set_pending_customer(batch, dict(
handler.set_customer(batch, dict(
full_name="Fred Flintstone",
first_name="Fred", last_name="Flintstone",
phone_number='555-1234', email_address='fred@mailinator.com',
created_by=user,
))
self.session.flush()
context = view.get_context_customer(batch)
@ -199,7 +319,7 @@ class TestOrderView(WebTestCase):
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': 'Fred Flintstone',
'new_customer_full_name': 'Fred Flintstone',
'new_customer_first_name': 'Fred',
'new_customer_last_name': 'Flintstone',
'new_customer_phone': '555-1234',
@ -217,11 +337,6 @@ class TestOrderView(WebTestCase):
'customer_name': None,
'phone_number': None,
'email_address': None,
'new_customer_name': None,
'new_customer_first_name': None,
'new_customer_last_name': None,
'new_customer_phone': None,
'new_customer_email': None,
})
def test_start_over(self):
@ -268,6 +383,80 @@ class TestOrderView(WebTestCase):
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
def test_assign_customer(self):
self.pyramid_config.add_route('orders.create', '/orders/new')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
weirdal = model.LocalCustomer(full_name="Weird Al")
self.session.add(weirdal)
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()
# normal
self.assertIsNone(batch.local_customer)
self.assertIsNone(batch.pending_customer)
context = view.assign_customer(batch, {'customer_id': weirdal.uuid.hex})
self.assertIsNone(batch.pending_customer)
self.assertIs(batch.local_customer, weirdal)
self.assertEqual(context, {
'customer_is_known': True,
'customer_id': weirdal.uuid.hex,
'customer_name': 'Weird Al',
'phone_number': None,
'email_address': None,
})
# missing customer_id
context = view.assign_customer(batch, {})
self.assertEqual(context, {'error': "Must provide customer_id"})
def test_unassign_customer(self):
self.pyramid_config.add_route('orders.create', '/orders/new')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
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()
view.set_pending_customer(batch, {'first_name': 'Jack',
'last_name': 'Black'})
# normal
self.assertIsNone(batch.local_customer)
self.assertIsNotNone(batch.pending_customer)
self.assertEqual(batch.customer_name, 'Jack Black')
context = view.unassign_customer(batch, {})
# nb. pending record remains, but not used
self.assertIsNotNone(batch.pending_customer)
self.assertIsNone(batch.customer_name)
self.assertIsNone(batch.local_customer)
self.assertEqual(context, {
'customer_is_known': True,
'customer_id': None,
'customer_name': None,
'phone_number': None,
'email_address': None,
'new_customer_full_name': 'Jack Black',
'new_customer_first_name': 'Jack',
'new_customer_last_name': 'Black',
'new_customer_phone': None,
'new_customer_email': None,
})
def test_set_pending_customer(self):
self.pyramid_config.add_route('orders.create', '/orders/new')
model = self.app.model
@ -301,19 +490,58 @@ class TestOrderView(WebTestCase):
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': 'Fred Flintstone',
'new_customer_full_name': 'Fred Flintstone',
'new_customer_first_name': 'Fred',
'new_customer_last_name': 'Flintstone',
'new_customer_phone': '555-1234',
'new_customer_email': 'fred@mailinator.com',
})
# error
with patch.object(handler, 'set_pending_customer', side_effect=RuntimeError):
context = view.set_pending_customer(batch, data)
self.assertEqual(context, {
'error': 'RuntimeError',
})
def test_get_product_info(self):
model = self.app.model
handler = self.make_handler()
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
local = model.LocalProduct(scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_price_reg=decimal.Decimal('5.99'))
self.session.add(local)
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
# typical, for local product
context = view.get_product_info(batch, {'product_id': local.uuid.hex})
self.assertEqual(context['product_id'], local.uuid.hex)
self.assertEqual(context['scancode'], '07430500132')
self.assertEqual(context['brand_name'], 'Bragg')
self.assertEqual(context['description'], 'Vinegar')
self.assertEqual(context['size'], '32oz')
self.assertEqual(context['full_description'], 'Bragg Vinegar 32oz')
self.assertEqual(context['case_size'], 12)
self.assertEqual(context['unit_price_reg'], 5.99)
# error if local product missing
mock_uuid = self.app.make_true_uuid()
context = view.get_product_info(batch, {'product_id': mock_uuid.hex})
self.assertEqual(context, {'error': "Product not found"})
# error if no product_id
context = view.get_product_info(batch, {})
self.assertEqual(context, {'error': "Must specify a product ID"})
# external lookup not implemented (yet)
with patch.object(handler, 'use_local_products', return_value=False):
self.assertRaises(NotImplementedError, view.get_product_info,
batch, {'product_id': '42'})
def test_add_item(self):
model = self.app.model
@ -326,7 +554,7 @@ class TestOrderView(WebTestCase):
self.session.commit()
data = {
'pending_product': {
'product_info': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
@ -353,16 +581,10 @@ class TestOrderView(WebTestCase):
row = batch.rows[0]
self.assertIsInstance(row.pending_product, model.PendingProduct)
# pending w/ invalid price
with patch.dict(data['pending_product'], unit_price_reg='invalid'):
result = view.add_item(batch, data)
self.assertEqual(result, {'error': "Invalid entry for field: unit_price_reg"})
self.session.flush()
self.assertEqual(len(batch.rows), 1) # still just the 1st row
# true product not yet supported
with patch.dict(data, product_is_known=True):
self.assertRaises(NotImplementedError, view.add_item, batch, data)
# external product not yet supported
with patch.object(handler, 'use_local_products', return_value=False):
with patch.dict(data, product_info='42'):
self.assertRaises(NotImplementedError, view.add_item, batch, data)
def test_update_item(self):
model = self.app.model
@ -375,7 +597,7 @@ class TestOrderView(WebTestCase):
self.session.commit()
data = {
'pending_product': {
'product_info': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
@ -403,7 +625,7 @@ class TestOrderView(WebTestCase):
# missing row uuid
result = view.update_item(batch, data)
self.assertEqual(result, {'error': "Must specify a row UUID"})
self.assertEqual(result, {'error': "Must specify row UUID"})
# row not found
with patch.dict(data, uuid=self.app.make_true_uuid()):
@ -420,16 +642,18 @@ class TestOrderView(WebTestCase):
result = view.update_item(batch, data)
self.assertEqual(result, {'error': "Row is for wrong batch"})
# set row for remaining tests
data['uuid'] = row.uuid
# true product not yet supported
with patch.dict(data, product_is_known=True):
self.assertRaises(NotImplementedError, view.update_item, batch, data)
with patch.object(handler, 'use_local_products', return_value=False):
self.assertRaises(NotImplementedError, view.update_item, batch, {
'uuid': row.uuid,
'product_info': '42',
'order_qty': 1,
'order_uom': enum.ORDER_UOM_UNIT,
})
# update row, pending product
with patch.dict(data, order_qty=2):
with patch.dict(data['pending_product'], scancode='07430500116'):
with patch.dict(data, uuid=row.uuid, order_qty=2):
with patch.dict(data['product_info'], scancode='07430500116'):
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.order_qty, 1)
result = view.update_item(batch, data)
@ -438,7 +662,7 @@ class TestOrderView(WebTestCase):
self.assertEqual(row.order_qty, 2)
self.assertEqual(row.pending_product.scancode, '07430500116')
self.assertEqual(result['row']['product_scancode'], '07430500116')
self.assertEqual(result['row']['order_qty'], '2')
self.assertEqual(result['row']['order_qty'], 2)
def test_delete_item(self):
model = self.app.model
@ -451,7 +675,7 @@ class TestOrderView(WebTestCase):
self.session.commit()
data = {
'pending_product': {
'product_info': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
@ -506,7 +730,7 @@ class TestOrderView(WebTestCase):
self.assertEqual(len(batch.rows), 0)
self.assertEqual(batch.row_count, 0)
def test_submit_new_order(self):
def test_submit_order(self):
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
model = self.app.model
enum = self.app.enum
@ -518,7 +742,7 @@ class TestOrderView(WebTestCase):
self.session.commit()
data = {
'pending_product': {
'product_info': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
@ -534,28 +758,33 @@ class TestOrderView(WebTestCase):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(len(batch.rows), 0)
# add row w/ pending product
view.add_item(batch, data)
self.session.flush()
self.assertEqual(len(batch.rows), 1)
row = batch.rows[0]
self.assertIsInstance(row.pending_product, model.PendingProduct)
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
# execute not allowed yet (no customer)
result = view.submit_new_order(batch, {})
result = view.submit_order(batch, {})
self.assertEqual(result, {'error': "Must assign the customer"})
# execute not allowed yet (no phone number)
view.set_pending_customer(batch, {'full_name': 'John Doe'})
result = view.submit_order(batch, {})
self.assertEqual(result, {'error': "Customer phone number is required"})
# submit/execute ok
batch.customer_id = 42
result = view.submit_new_order(batch, {})
view.set_pending_customer(batch, {'full_name': 'John Doe',
'phone_number': '555-1234'})
result = view.submit_order(batch, {})
self.assertEqual(sorted(result), ['next_url'])
self.assertIn('/orders/', result['next_url'])
# error (already executed)
result = view.submit_new_order(batch, {})
result = view.submit_order(batch, {})
self.assertEqual(result, {
'error': f"ValueError: batch has already been executed: {batch}",
})
@ -585,9 +814,8 @@ class TestOrderView(WebTestCase):
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
'created_by': user,
}
row = handler.add_pending_product(batch, pending, 1, enum.ORDER_UOM_CASE)
row = handler.add_item(batch, pending, 1, enum.ORDER_UOM_CASE)
self.session.commit()
data = view.normalize_batch(batch)
@ -604,11 +832,15 @@ class TestOrderView(WebTestCase):
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
view.batch_handler = handler
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.session.flush()
# add 1st row w/ pending product
pending = {
'scancode': '07430500132',
'brand_name': 'Bragg',
@ -616,19 +848,22 @@ class TestOrderView(WebTestCase):
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
'created_by': user,
'vendor_name': 'Acme Warehouse',
'vendor_item_code': '1234',
}
row = handler.add_pending_product(batch, pending, 2, enum.ORDER_UOM_CASE)
self.session.commit()
row1 = handler.add_item(batch, pending, 2, enum.ORDER_UOM_CASE)
# normal
data = view.normalize_row(row)
# typical, pending product
data = view.normalize_row(row1)
self.assertIsInstance(data, dict)
self.assertEqual(data['uuid'], row.uuid.hex)
self.assertEqual(data['uuid'], row1.uuid.hex)
self.assertEqual(data['sequence'], 1)
self.assertIsNone(data['product_id'])
self.assertEqual(data['product_scancode'], '07430500132')
self.assertEqual(data['case_size'], '12')
self.assertEqual(data['order_qty'], '2')
self.assertEqual(data['product_full_description'], 'Bragg Vinegar 32oz')
self.assertEqual(data['case_size'], 12)
self.assertEqual(data['vendor_name'], 'Acme Warehouse')
self.assertEqual(data['order_qty'], 2)
self.assertEqual(data['order_uom'], 'CS')
self.assertEqual(data['order_qty_display'], '2 Cases (× 12 = 24 Units)')
self.assertEqual(data['unit_price_reg'], 5.99)
@ -644,9 +879,9 @@ class TestOrderView(WebTestCase):
self.assertEqual(data['total_price'], 143.76)
self.assertEqual(data['total_price_display'], '$143.76')
self.assertIsNone(data['special_order'])
self.assertEqual(data['status_code'], row.STATUS_OK)
self.assertEqual(data['status_code'], row1.STATUS_OK)
self.assertEqual(data['pending_product'], {
'uuid': row.pending_product_uuid.hex,
'uuid': row1.pending_product_uuid.hex,
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
@ -654,44 +889,117 @@ class TestOrderView(WebTestCase):
'department_id': None,
'department_name': None,
'unit_price_reg': 5.99,
'vendor_name': None,
'vendor_item_code': None,
'vendor_name': 'Acme Warehouse',
'vendor_item_code': '1234',
'unit_cost': None,
'case_size': 12.0,
'notes': None,
'special_order': None,
})
# the next few tests will morph 1st row..
# unknown case size
row.pending_product.case_size = None
handler.refresh_row(row)
row1.pending_product.case_size = None
handler.refresh_row(row1)
self.session.flush()
data = view.normalize_row(row)
data = view.normalize_row(row1)
self.assertIsNone(data['case_size'])
self.assertEqual(data['order_qty_display'], '2 Cases (× ?? = ?? Units)')
# order by unit
row.order_uom = enum.ORDER_UOM_UNIT
handler.refresh_row(row)
row1.order_uom = enum.ORDER_UOM_UNIT
handler.refresh_row(row1)
self.session.flush()
data = view.normalize_row(row)
data = view.normalize_row(row1)
self.assertEqual(data['order_uom'], enum.ORDER_UOM_UNIT)
self.assertEqual(data['order_qty_display'], '2 Units')
# item on sale
row.pending_product.case_size = 12
row.unit_price_sale = decimal.Decimal('5.19')
row.sale_ends = datetime.datetime(2025, 1, 5, 20, 32)
handler.refresh_row(row, now=datetime.datetime(2025, 1, 5, 19))
row1.pending_product.case_size = 12
row1.unit_price_sale = decimal.Decimal('5.19')
row1.sale_ends = datetime.datetime(2099, 1, 5, 20, 32)
handler.refresh_row(row1)
self.session.flush()
data = view.normalize_row(row)
data = view.normalize_row(row1)
self.assertEqual(data['unit_price_sale'], 5.19)
self.assertEqual(data['unit_price_sale_display'], '$5.19')
self.assertEqual(data['sale_ends'], '2025-01-05 20:32:00')
self.assertEqual(data['sale_ends_display'], '2025-01-05')
self.assertEqual(data['sale_ends'], '2099-01-05 20:32:00')
self.assertEqual(data['sale_ends_display'], '2099-01-05')
self.assertEqual(data['unit_price_quoted'], 5.19)
self.assertEqual(data['unit_price_quoted_display'], '$5.19')
self.assertEqual(data['case_price_quoted'], 62.28)
self.assertEqual(data['case_price_quoted_display'], '$62.28')
# add 2nd row w/ local product
local = model.LocalProduct(brand_name="Lay's",
description="Potato Chips",
vendor_name='Acme Distribution',
unit_price_reg=3.29)
self.session.add(local)
self.session.flush()
row2 = handler.add_item(batch, local.uuid.hex, 1, enum.ORDER_UOM_UNIT)
# typical, local product
data = view.normalize_row(row2)
self.assertEqual(data['uuid'], row2.uuid.hex)
self.assertEqual(data['sequence'], 2)
self.assertEqual(data['product_id'], local.uuid.hex)
self.assertIsNone(data['product_scancode'])
self.assertEqual(data['product_full_description'], "Lay's Potato Chips")
self.assertIsNone(data['case_size'])
self.assertEqual(data['vendor_name'], 'Acme Distribution')
self.assertEqual(data['order_qty'], 1)
self.assertEqual(data['order_uom'], 'EA')
self.assertEqual(data['order_qty_display'], '1 Units')
self.assertEqual(data['unit_price_reg'], 3.29)
self.assertEqual(data['unit_price_reg_display'], '$3.29')
self.assertNotIn('unit_price_sale', data)
self.assertNotIn('unit_price_sale_display', data)
self.assertNotIn('sale_ends', data)
self.assertNotIn('sale_ends_display', data)
self.assertEqual(data['unit_price_quoted'], 3.29)
self.assertEqual(data['unit_price_quoted_display'], '$3.29')
self.assertIsNone(data['case_price_quoted'])
self.assertEqual(data['case_price_quoted_display'], '')
self.assertEqual(data['total_price'], 3.29)
self.assertEqual(data['total_price_display'], '$3.29')
self.assertIsNone(data['special_order'])
self.assertEqual(data['status_code'], row2.STATUS_OK)
self.assertNotIn('pending_product', data)
# the next few tests will morph 2nd row..
# typical, external product
row2.product_id = '42'
with patch.object(handler, 'use_local_products', return_value=False):
data = view.normalize_row(row2)
self.assertEqual(data['uuid'], row2.uuid.hex)
self.assertEqual(data['sequence'], 2)
self.assertEqual(data['product_id'], '42')
self.assertIsNone(data['product_scancode'])
self.assertNotIn('product_full_description', data) # TODO
self.assertIsNone(data['case_size'])
self.assertNotIn('vendor_name', data) # TODO
self.assertEqual(data['order_qty'], 1)
self.assertEqual(data['order_uom'], 'EA')
self.assertEqual(data['order_qty_display'], '1 Units')
self.assertEqual(data['unit_price_reg'], 3.29)
self.assertEqual(data['unit_price_reg_display'], '$3.29')
self.assertNotIn('unit_price_sale', data)
self.assertNotIn('unit_price_sale_display', data)
self.assertNotIn('sale_ends', data)
self.assertNotIn('sale_ends_display', data)
self.assertEqual(data['unit_price_quoted'], 3.29)
self.assertEqual(data['unit_price_quoted_display'], '$3.29')
self.assertIsNone(data['case_price_quoted'])
self.assertEqual(data['case_price_quoted_display'], '')
self.assertEqual(data['total_price'], 3.29)
self.assertEqual(data['total_price_display'], '$3.29')
self.assertIsNone(data['special_order'])
self.assertEqual(data['status_code'], row2.STATUS_OK)
self.assertNotIn('pending_product', data)
def test_get_instance_title(self):
model = self.app.model
view = self.make_view()
@ -715,13 +1023,31 @@ class TestOrderView(WebTestCase):
self.session.add(order)
self.session.commit()
# viewing
# viewing (no customer)
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=order)
# nb. this is to avoid include/exclude ambiguity
form.remove('items')
view.configure_form(form)
schema = form.get_schema()
self.assertIn('pending_customer', form)
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
# assign local customer
local = model.LocalCustomer(first_name='Jack', last_name='Black',
phone_number='555-1234')
self.session.add(local)
order.local_customer = local
self.session.flush()
# viewing (local customer)
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=order)
# nb. this is to avoid include/exclude ambiguity
form.remove('items')
view.configure_form(form)
self.assertNotIn('pending_customer', form)
schema = form.get_schema()
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
def test_get_xref_buttons(self):
@ -831,6 +1157,46 @@ class TestOrderView(WebTestCase):
url = view.get_row_action_url_view(item, 0)
self.assertIn(f'/order-items/{item.uuid}', url)
def test_configure(self):
self.pyramid_config.add_route('home', '/')
self.pyramid_config.add_route('login', '/auth/login')
self.pyramid_config.add_route('orders', '/orders/')
model = self.app.model
view = self.make_view()
with patch.object(view, 'Session', return_value=self.session):
with patch.multiple(self.config, usedb=True, preferdb=True):
# sanity check
allowed = self.config.get_bool('sideshow.orders.allow_unknown_products',
session=self.session)
self.assertIsNone(allowed)
self.assertEqual(self.session.query(model.Setting).count(), 0)
# fetch initial page
response = view.configure()
self.assertIsInstance(response, Response)
self.assertNotIsInstance(response, HTTPFound)
self.session.flush()
allowed = self.config.get_bool('sideshow.orders.allow_unknown_products',
session=self.session)
self.assertIsNone(allowed)
self.assertEqual(self.session.query(model.Setting).count(), 0)
# post new settings
with patch.multiple(self.request, create=True,
method='POST',
POST={
'sideshow.orders.allow_unknown_products': 'true',
}):
response = view.configure()
self.assertIsInstance(response, HTTPFound)
self.session.flush()
allowed = self.config.get_bool('sideshow.orders.allow_unknown_products',
session=self.session)
self.assertTrue(allowed)
self.assertTrue(self.session.query(model.Setting).count() > 1)
class TestOrderItemView(WebTestCase):
@ -864,6 +1230,18 @@ class TestOrderItemView(WebTestCase):
self.assertEqual(view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED),
'initiated')
def test_get_instance_title(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
item = model.OrderItem(product_brand='Bragg',
product_description='Vinegar',
product_size='32oz',
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
title = view.get_instance_title(item)
self.assertEqual(title, "(initiated) Bragg Vinegar 32oz")
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
@ -871,12 +1249,24 @@ class TestOrderItemView(WebTestCase):
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_INITIATED)
# viewing
# viewing, w/ pending product
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=item)
view.configure_form(form)
schema = form.get_schema()
self.assertIsInstance(schema['order'].typ, OrderRef)
self.assertIn('pending_product', form)
self.assertIsInstance(schema['pending_product'].typ, PendingProductRef)
# viewing, w/ local product
local = model.LocalProduct()
item.local_product = local
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=item)
view.configure_form(form)
schema = form.get_schema()
self.assertIsInstance(schema['order'].typ, OrderRef)
self.assertNotIn('pending_product', form)
def test_get_xref_buttons(self):
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')

View file

@ -16,6 +16,104 @@ class TestIncludeme(WebTestCase):
mod.includeme(self.pyramid_config)
class TestLocalProductView(WebTestCase):
def make_view(self):
return mod.LocalProductView(self.request)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.LocalProduct)
self.assertNotIn('scancode', grid.linked_columns)
self.assertNotIn('brand_name', grid.linked_columns)
self.assertNotIn('description', grid.linked_columns)
view.configure_grid(grid)
self.assertIn('scancode', grid.linked_columns)
self.assertIn('brand_name', grid.linked_columns)
self.assertIn('description', grid.linked_columns)
def test_configure_form(self):
model = self.app.model
view = self.make_view()
# creating
with patch.object(view, 'creating', new=True):
form = view.make_form(model_class=model.LocalProduct)
self.assertIn('external_id', form)
view.configure_form(form)
self.assertNotIn('external_id', form)
user = model.User(username='barney')
self.session.add(user)
product = model.LocalProduct()
self.session.add(product)
self.session.commit()
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=product)
self.assertNotIn('external_id', form.readonly_fields)
self.assertNotIn('local_products.view.orders', form.grid_vue_context)
view.configure_form(form)
self.assertIn('external_id', form.readonly_fields)
self.assertIn('local_products.view.orders', form.grid_vue_context)
def test_make_orders_grid(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, customer_id=42, created_by=user)
product = model.LocalProduct()
self.session.add(product)
item = model.OrderItem(local_product=product,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
order.items.append(item)
self.session.add(order)
self.session.commit()
# no view perm
grid = view.make_orders_grid(product)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_orders_grid(product)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_make_new_order_batches_grid(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
product = model.LocalProduct()
self.session.add(product)
row = handler.make_row(local_product=product,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.commit()
# no view perm
grid = view.make_new_order_batches_grid(product)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_new_order_batches_grid(product)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
class TestPendingProductView(WebTestCase):
def make_view(self):