# -*- coding: utf-8; -*- import datetime import decimal from unittest.mock import patch from sqlalchemy import orm from pyramid.httpexceptions import HTTPForbidden, HTTPFound from pyramid.response import Response 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, PendingProductRef class TestIncludeme(WebTestCase): def test_coverage(self): mod.includeme(self.pyramid_config) 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() grid = view.make_grid(model_class=model.PendingProduct) self.assertNotIn('order_id', grid.linked_columns) self.assertNotIn('total_price', grid.renderers) view.configure_grid(grid) self.assertIn('order_id', grid.linked_columns) self.assertIn('total_price', grid.renderers) def test_create(self): self.pyramid_config.include('sideshow.web.views') self.config.setdefault('wutta.batch.neworder.handler.spec', 'sideshow.batch.neworder:NewOrderBatchHandler') model = self.app.model enum = self.app.enum view = self.make_view() user = model.User(username='barney') self.session.add(user) self.session.flush() with patch.object(view, 'Session', return_value=self.session): with patch.object(self.request, 'current_route_url', return_value='/orders/new'): # this will require some perms with patch.multiple(self.request, create=True, user=user, is_root=True): # fetch page to start things off self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0) response = view.create() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1) batch1 = self.session.query(model.NewOrderBatch).one() # start over; deletes current batch with patch.multiple(self.request, create=True, method='POST', POST={'action': 'start_over'}): response = view.create() self.assertIsInstance(response, HTTPFound) self.assertIn('/orders/new', response.location) self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0) # fetch again to get new batch response = view.create() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1) batch2 = self.session.query(model.NewOrderBatch).one() self.assertIsNot(batch2, batch1) # set pending customer with patch.multiple(self.request, create=True, method='POST', json_body={'action': 'set_pending_customer', 'first_name': 'Fred', 'last_name': 'Flintstone', 'phone_number': '555-1234', 'email_address': 'fred@mailinator.com'}): response = view.create() self.assertIsInstance(response, Response) self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json_body, { 'customer_is_known': False, 'customer_id': None, 'customer_name': 'Fred Flintstone', 'phone_number': '555-1234', 'email_address': 'fred@mailinator.com', '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', }) # invalid action with patch.multiple(self.request, create=True, method='POST', POST={'action': 'bogus'}, json_body={'action': 'bogus'}): response = view.create() self.assertIsInstance(response, Response) 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) view = self.make_view() # user is required self.assertRaises(HTTPForbidden, view.get_current_batch) user = model.User(username='barney') self.session.add(user) self.session.commit() 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 is auto-created self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0) batch = view.get_current_batch() self.session.flush() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1) self.assertIs(batch.created_by, user) # same batch is returned subsequently batch2 = view.get_current_batch() self.session.flush() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1) self.assertIs(batch2, batch) def test_customer_autocomplete(self): model = self.app.model handler = self.make_handler() view = self.make_view() view.batch_handler = handler 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, []) # external lookup not implemented by default with patch.object(handler, 'use_local_customers', return_value=False): with patch.object(self.request, 'GET', new={'term': 'sally'}, create=True): self.assertRaises(NotImplementedError, view.customer_autocomplete) def test_product_autocomplete(self): model = self.app.model handler = self.make_handler() view = self.make_view() view.batch_handler = handler 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, []) # external lookup not implemented by default with patch.object(handler, 'use_local_products', return_value=False): with patch.object(self.request, 'GET', new={'term': 'juice'}, create=True): self.assertRaises(NotImplementedError, view.product_autocomplete) def test_get_pending_product_required_fields(self): model = self.app.model view = self.make_view() # only description is required by default fields = view.get_pending_product_required_fields() self.assertEqual(fields, ['description']) # but config can specify otherwise self.config.setdefault('sideshow.orders.unknown_product.fields.brand_name.required', 'true') self.config.setdefault('sideshow.orders.unknown_product.fields.description.required', 'false') self.config.setdefault('sideshow.orders.unknown_product.fields.size.required', 'true') self.config.setdefault('sideshow.orders.unknown_product.fields.unit_price_reg.required', 'true') fields = view.get_pending_product_required_fields() self.assertEqual(fields, ['brand_name', 'size', 'unit_price_reg']) def test_get_context_customer(self): self.pyramid_config.add_route('orders', '/orders/') 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 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, 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': 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_customer(batch, dict( full_name="Fred Flintstone", first_name="Fred", last_name="Flintstone", phone_number='555-1234', email_address='fred@mailinator.com', )) self.session.flush() context = view.get_context_customer(batch) self.assertEqual(context, { 'customer_is_known': False, 'customer_id': None, 'customer_name': 'Fred Flintstone', 'phone_number': '555-1234', 'email_address': 'fred@mailinator.com', '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', }) # with no customer batch = handler.make_batch(self.session, created_by=user) self.session.add(batch) self.session.flush() context = view.get_context_customer(batch) self.assertEqual(context, { 'customer_is_known': True, # nb. this is for UI default 'customer_id': None, 'customer_name': None, 'phone_number': None, 'email_address': None, }) def test_start_over(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.commit() 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.session.flush() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1) result = view.start_over(batch) self.assertIsInstance(result, HTTPFound) self.session.flush() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0) def test_cancel_order(self): self.pyramid_config.add_route('orders', '/orders/') model = self.app.model handler = NewOrderBatchHandler(self.config) view = self.make_view() user = model.User(username='barney') self.session.add(user) self.session.commit() 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.session.flush() self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1) result = view.cancel_order(batch) self.assertIsInstance(result, HTTPFound) 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 handler = NewOrderBatchHandler(self.config) view = self.make_view() user = model.User(username='barney') self.session.add(user) self.session.commit() data = { 'first_name': 'Fred', 'last_name': 'Flintstone', 'phone_number': '555-1234', 'email_address': 'fred@mailinator.com', } 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.session.flush() # normal self.assertIsNone(batch.pending_customer) context = view.set_pending_customer(batch, data) self.assertIsInstance(batch.pending_customer, model.PendingCustomer) self.assertEqual(context, { 'customer_is_known': False, 'customer_id': None, 'customer_name': 'Fred Flintstone', 'phone_number': '555-1234', 'email_address': 'fred@mailinator.com', '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', }) 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 no product_id context = view.get_product_info(batch, {}) self.assertEqual(context, {'error': "Must specify a product ID"}) # error if product not found mock_uuid = self.app.make_true_uuid() self.assertRaises(ValueError, view.get_product_info, batch, {'product_id': mock_uuid.hex}) with patch.object(handler, 'use_local_products', return_value=False): # external lookup not implemented by default self.assertRaises(NotImplementedError, view.get_product_info, batch, {'product_id': '42'}) # external lookup may return its own error with patch.object(handler, 'get_product_info_external', return_value={'error': "something smells fishy"}): context = view.get_product_info(batch, {'product_id': '42'}) self.assertEqual(context, {'error': "something smells fishy"}) def test_add_item(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) self.session.commit() data = { 'product_info': { 'scancode': '07430500132', 'brand_name': 'Bragg', 'description': 'Vinegar', 'size': '32oz', 'unit_price_reg': 5.99, }, 'order_qty': 1, 'order_uom': enum.ORDER_UOM_UNIT, } 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.session.flush() self.assertEqual(len(batch.rows), 0) # normal pending product result = view.add_item(batch, data) self.assertIn('batch', result) self.assertIn('row', result) self.session.flush() self.assertEqual(len(batch.rows), 1) row = batch.rows[0] self.assertIsInstance(row.pending_product, model.PendingProduct) # 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 enum = self.app.enum handler = NewOrderBatchHandler(self.config) view = self.make_view() user = model.User(username='barney') self.session.add(user) self.session.commit() data = { 'product_info': { 'scancode': '07430500132', 'brand_name': 'Bragg', 'description': 'Vinegar', 'size': '32oz', 'unit_price_reg': 5.99, 'case_size': 12, }, 'order_qty': 1, 'order_uom': enum.ORDER_UOM_CASE, } 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.session.flush() self.assertEqual(len(batch.rows), 0) # add row w/ pending product view.add_item(batch, data) self.session.flush() row = batch.rows[0] self.assertIsInstance(row.pending_product, model.PendingProduct) self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99')) # missing row uuid result = view.update_item(batch, data) self.assertEqual(result, {'error': "Must specify row UUID"}) # row not found with patch.dict(data, uuid=self.app.make_true_uuid()): result = view.update_item(batch, data) self.assertEqual(result, {'error': "Row not found"}) # row for wrong batch batch2 = handler.make_batch(self.session, created_by=user) self.session.add(batch2) row2 = handler.make_row(order_qty=1, order_uom=enum.ORDER_UOM_UNIT) handler.add_row(batch2, row2) self.session.flush() with patch.dict(data, uuid=row2.uuid): result = view.update_item(batch, data) self.assertEqual(result, {'error': "Row is for wrong batch"}) # true product not yet supported 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, 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) self.assertEqual(sorted(result), ['batch', 'row']) self.assertEqual(row.product_scancode, '07430500116') 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) def test_delete_item(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) self.session.commit() data = { 'product_info': { 'scancode': '07430500132', 'brand_name': 'Bragg', 'description': 'Vinegar', 'size': '32oz', 'unit_price_reg': 5.99, 'case_size': 12, }, 'order_qty': 1, 'order_uom': enum.ORDER_UOM_CASE, } 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.session.flush() self.assertEqual(len(batch.rows), 0) # add row w/ pending product view.add_item(batch, data) self.session.flush() row = batch.rows[0] self.assertIsInstance(row.pending_product, model.PendingProduct) self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99')) # missing row uuid result = view.delete_item(batch, data) self.assertEqual(result, {'error': "Must specify a row UUID"}) # row not found with patch.dict(data, uuid=self.app.make_true_uuid()): result = view.delete_item(batch, data) self.assertEqual(result, {'error': "Row not found"}) # row for wrong batch batch2 = handler.make_batch(self.session, created_by=user) self.session.add(batch2) row2 = handler.make_row(order_qty=1, order_uom=enum.ORDER_UOM_UNIT) handler.add_row(batch2, row2) self.session.flush() with patch.dict(data, uuid=row2.uuid): result = view.delete_item(batch, data) self.assertEqual(result, {'error': "Row is for wrong batch"}) # row is deleted data['uuid'] = row.uuid self.assertEqual(len(batch.rows), 1) self.assertEqual(batch.row_count, 1) result = view.delete_item(batch, data) self.assertEqual(sorted(result), ['batch']) self.session.refresh(batch) self.assertEqual(len(batch.rows), 0) self.assertEqual(batch.row_count, 0) def test_submit_order(self): self.pyramid_config.add_route('orders.view', '/orders/{uuid}') 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) self.session.commit() data = { 'product_info': { 'scancode': '07430500132', 'brand_name': 'Bragg', 'description': 'Vinegar', 'size': '32oz', 'unit_price_reg': 5.99, 'case_size': 12, }, 'order_qty': 1, 'order_uom': enum.ORDER_UOM_CASE, } 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.assertEqual(len(batch.rows), 0) # add row w/ pending product view.add_item(batch, data) 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_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 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_order(batch, {}) self.assertEqual(result, { 'error': f"ValueError: batch has already been executed: {batch}", }) def test_get_default_uom_choices(self): enum = self.app.enum view = self.make_view() uoms = view.get_default_uom_choices() self.assertEqual(uoms, [{'key': key, 'value': val} for key, val in enum.ORDER_UOM.items()]) def test_normalize_batch(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) pending = { 'scancode': '07430500132', 'brand_name': 'Bragg', 'description': 'Vinegar', 'size': '32oz', 'unit_price_reg': 5.99, 'case_size': 12, } row = handler.add_item(batch, pending, 1, enum.ORDER_UOM_CASE) self.session.commit() data = view.normalize_batch(batch) self.assertEqual(data, { 'uuid': batch.uuid.hex, 'total_price': '71.880', 'total_price_display': '$71.88', 'status_code': None, 'status_text': None, }) def test_normalize_row(self): model = self.app.model 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', 'description': 'Vinegar', 'size': '32oz', 'unit_price_reg': 5.99, 'case_size': 12, 'vendor_name': 'Acme Warehouse', 'vendor_item_code': '1234', } row1 = handler.add_item(batch, pending, 2, enum.ORDER_UOM_CASE) # typical, pending product data = view.normalize_row(row1) self.assertIsInstance(data, dict) 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['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) self.assertEqual(data['unit_price_reg_display'], '$5.99') 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'], 5.99) self.assertEqual(data['unit_price_quoted_display'], '$5.99') self.assertEqual(data['case_price_quoted'], 71.88) self.assertEqual(data['case_price_quoted_display'], '$71.88') 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'], row1.STATUS_OK) self.assertEqual(data['pending_product'], { 'uuid': row1.pending_product_uuid.hex, 'scancode': '07430500132', 'brand_name': 'Bragg', 'description': 'Vinegar', 'size': '32oz', 'department_id': None, 'department_name': None, 'unit_price_reg': 5.99, '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 row1.pending_product.case_size = None handler.refresh_row(row1) self.session.flush() data = view.normalize_row(row1) self.assertIsNone(data['case_size']) self.assertEqual(data['order_qty_display'], '2 Cases (× ?? = ?? Units)') # order by unit row1.order_uom = enum.ORDER_UOM_UNIT handler.refresh_row(row1) self.session.flush() 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 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(row1) self.assertEqual(data['unit_price_sale'], 5.19) self.assertEqual(data['unit_price_sale_display'], '$5.19') 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.. def refresh_external(row): row.product_scancode = '012345' row.product_brand = 'Acme' row.product_description = 'Bricks' row.product_size = '1 ton' row.product_weighed = True row.department_id = 1 row.department_name = "Bricks & Mortar" row.special_order = False row.case_size = None row.unit_cost = decimal.Decimal('599.99') row.unit_price_reg = decimal.Decimal('999.99') # typical, external product with patch.object(handler, 'use_local_products', return_value=False): with patch.object(handler, 'refresh_row_from_external_product', new=refresh_external): handler.update_item(row2, '42', 1, enum.ORDER_UOM_UNIT) data = view.normalize_row(row2) self.assertEqual(data['uuid'], row2.uuid.hex) self.assertEqual(data['sequence'], 2) self.assertEqual(data['product_id'], '42') 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['order_qty'], 1) self.assertEqual(data['order_uom'], 'EA') self.assertEqual(data['order_qty_display'], '1 Units') self.assertEqual(data['unit_price_reg'], 999.99) self.assertEqual(data['unit_price_reg_display'], '$999.99') 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'], 999.99) self.assertEqual(data['unit_price_quoted_display'], '$999.99') self.assertIsNone(data['case_price_quoted']) self.assertEqual(data['case_price_quoted_display'], '') self.assertEqual(data['total_price'], 999.99) self.assertEqual(data['total_price_display'], '$999.99') self.assertFalse(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() user = model.User(username='barney') self.session.add(user) order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user) self.session.add(order) self.session.flush() title = view.get_instance_title(order) self.assertEqual(title, "#42 for Fred Flintstone") def test_configure_form(self): model = self.app.model view = self.make_view() user = model.User(username='barney') self.session.add(user) order = model.Order(order_id=42, created_by=user) self.session.add(order) self.session.commit() # 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): self.pyramid_config.add_route('neworder_batches.view', '/batch/neworder/{uuid}') model = self.app.model handler = NewOrderBatchHandler(self.config) view = self.make_view() user = model.User(username='barney') self.session.add(user) order = model.Order(order_id=42, created_by=user) self.session.add(order) self.session.flush() with patch.object(view, 'Session', return_value=self.session): # nb. this requires perm to view batch with patch.object(self.request, 'is_root', new=True): # order has no batch, so no buttons buttons = view.get_xref_buttons(order) self.assertEqual(buttons, []) # mock up a batch to get a button batch = handler.make_batch(self.session, id=order.order_id, created_by=user, executed=datetime.datetime.now(), executed_by=user) self.session.add(batch) self.session.flush() buttons = view.get_xref_buttons(order) self.assertEqual(len(buttons), 1) button = buttons[0] self.assertIn("View the Batch", button) def test_get_row_grid_data(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, created_by=user) self.session.add(order) self.session.flush() order.items.append(model.OrderItem(product_id='07430500132', product_scancode='07430500132', order_qty=1, order_uom=enum.ORDER_UOM_UNIT, status_code=enum.ORDER_ITEM_STATUS_INITIATED)) self.session.flush() with patch.object(view, 'Session', return_value=self.session): query = view.get_row_grid_data(order) self.assertIsInstance(query, orm.Query) items = query.all() self.assertEqual(len(items), 1) self.assertEqual(items[0].product_scancode, '07430500132') def test_configure_row_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, created_by=user) self.session.add(order) self.session.flush() order.items.append(model.OrderItem(product_id='07430500132', product_scancode='07430500132', order_qty=1, order_uom=enum.ORDER_UOM_UNIT, status_code=enum.ORDER_ITEM_STATUS_INITIATED)) self.session.flush() with patch.object(view, 'Session', return_value=self.session): grid = view.make_grid(model_class=model.OrderItem, data=order.items) self.assertNotIn('product_scancode', grid.linked_columns) view.configure_row_grid(grid) self.assertIn('product_scancode', grid.linked_columns) def test_render_status_code(self): enum = self.app.enum view = self.make_view() result = view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED) self.assertEqual(result, "initiated") self.assertEqual(result, enum.ORDER_ITEM_STATUS[enum.ORDER_ITEM_STATUS_INITIATED]) def test_get_row_action_url_view(self): self.pyramid_config.add_route('order_items.view', '/order-items/{uuid}') 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, created_by=user) self.session.add(order) self.session.flush() item = model.OrderItem(product_id='07430500132', product_scancode='07430500132', order_qty=1, order_uom=enum.ORDER_UOM_UNIT, status_code=enum.ORDER_ITEM_STATUS_INITIATED) order.items.append(item) self.session.flush() 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): def make_view(self): return mod.OrderItemView(self.request) def test_get_query(self): view = self.make_view() query = view.get_query(session=self.session) self.assertIsInstance(query, orm.Query) def test_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_render_order_id(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) def test_render_status_code(self): enum = self.app.enum view = self.make_view() 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 view = self.make_view() item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_INITIATED) # 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}') 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, created_by=user) self.session.add(order) item = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT, status_code=enum.ORDER_ITEM_STATUS_INITIATED) order.items.append(item) self.session.flush() # nb. this requires perms with patch.object(self.request, 'is_root', new=True): # one button by default buttons = view.get_xref_buttons(item) self.assertEqual(len(buttons), 1) self.assertIn("View the Order", buttons[0])