2025-01-06 17:03:41 -06:00
|
|
|
# -*- coding: utf-8; -*-
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import decimal
|
2025-01-30 21:45:10 -06:00
|
|
|
import json
|
2025-01-06 17:03:41 -06:00
|
|
|
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
|
2025-01-15 16:57:28 -06:00
|
|
|
from sideshow.orders import OrderHandler
|
2025-01-06 17:03:41 -06:00
|
|
|
from sideshow.testing import WebTestCase
|
|
|
|
from sideshow.web.views import orders as mod
|
2025-01-09 12:13:58 -06:00
|
|
|
from sideshow.web.forms.schema import OrderRef, PendingProductRef
|
2025-02-01 19:39:02 -06:00
|
|
|
from sideshow.config import SideshowConfig
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
|
|
|
|
class TestIncludeme(WebTestCase):
|
|
|
|
|
|
|
|
def test_coverage(self):
|
|
|
|
mod.includeme(self.pyramid_config)
|
|
|
|
|
|
|
|
|
|
|
|
class TestOrderView(WebTestCase):
|
|
|
|
|
2025-02-01 19:39:02 -06:00
|
|
|
def make_config(self, **kw):
|
|
|
|
config = super().make_config(**kw)
|
|
|
|
SideshowConfig().configure(config)
|
|
|
|
return config
|
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
def make_view(self):
|
|
|
|
return mod.OrderView(self.request)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def make_handler(self):
|
|
|
|
return NewOrderBatchHandler(self.config)
|
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
def test_configure_grid(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
2025-01-27 20:33:14 -06:00
|
|
|
|
|
|
|
# store_id hidden by default
|
2025-08-31 12:59:28 -05:00
|
|
|
grid = view.make_grid(model_class=model.Order, columns=["store_id", "order_id"])
|
|
|
|
self.assertIn("store_id", grid.columns)
|
2025-01-06 17:03:41 -06:00
|
|
|
view.configure_grid(grid)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertNotIn("store_id", grid.columns)
|
2025-01-27 20:33:14 -06:00
|
|
|
|
|
|
|
# store_id is shown if configured
|
2025-08-31 12:59:28 -05:00
|
|
|
self.config.setdefault("sideshow.orders.expose_store_id", "true")
|
|
|
|
grid = view.make_grid(model_class=model.Order, columns=["store_id", "order_id"])
|
|
|
|
self.assertIn("store_id", grid.columns)
|
2025-01-27 20:33:14 -06:00
|
|
|
view.configure_grid(grid)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("store_id", grid.columns)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_create(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.include("sideshow.web.views")
|
|
|
|
self.config.setdefault(
|
|
|
|
"wutta.batch.neworder.handler.spec",
|
|
|
|
"sideshow.batch.neworder:NewOrderBatchHandler",
|
|
|
|
)
|
|
|
|
self.config.setdefault("sideshow.orders.expose_store_id", "true")
|
|
|
|
self.config.setdefault("sideshow.orders.allow_item_discounts", "true")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
2025-01-09 12:13:58 -06:00
|
|
|
enum = self.app.enum
|
2025-01-06 17:03:41 -06:00
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
store = model.Store(store_id="001", name="Acme Goods")
|
2025-01-27 20:33:14 -06:00
|
|
|
self.session.add(store)
|
2025-08-31 12:59:28 -05:00
|
|
|
store = model.Store(store_id="002", name="Acme Services")
|
2025-01-27 20:33:14 -06:00
|
|
|
self.session.add(store)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
|
|
|
with patch.object(
|
|
|
|
self.request, "current_route_url", return_value="/orders/new"
|
|
|
|
):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# this will require some perms
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, create=True, user=user, is_root=True):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# 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
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(
|
|
|
|
self.request,
|
|
|
|
create=True,
|
|
|
|
method="POST",
|
|
|
|
POST={"action": "start_over"},
|
|
|
|
):
|
2025-01-06 17:03:41 -06:00
|
|
|
response = view.create()
|
|
|
|
self.assertIsInstance(response, HTTPFound)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("/orders/new", response.location)
|
|
|
|
self.assertEqual(
|
|
|
|
self.session.query(model.NewOrderBatch).count(), 0
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# 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
|
2025-08-31 12:59:28 -05:00
|
|
|
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",
|
|
|
|
},
|
|
|
|
):
|
2025-01-06 17:03:41 -06:00
|
|
|
response = view.create()
|
|
|
|
self.assertIsInstance(response, Response)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(response.content_type, "application/json")
|
|
|
|
self.assertEqual(
|
|
|
|
response.json_body,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"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",
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# invalid action
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(
|
|
|
|
self.request,
|
|
|
|
create=True,
|
|
|
|
method="POST",
|
|
|
|
POST={"action": "bogus"},
|
|
|
|
json_body={"action": "bogus"},
|
|
|
|
):
|
2025-01-06 17:03:41 -06:00
|
|
|
response = view.create()
|
|
|
|
self.assertIsInstance(response, Response)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(response.content_type, "application/json")
|
|
|
|
self.assertEqual(
|
|
|
|
response.json_body, {"error": "unknown form action"}
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# add item
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
):
|
2025-01-09 12:13:58 -06:00
|
|
|
response = view.create()
|
|
|
|
self.assertIsInstance(response, Response)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(response.content_type, "application/json")
|
2025-01-09 12:13:58 -06:00
|
|
|
data = response.json_body
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(sorted(data), ["batch", "row"])
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# add item, w/ error
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
):
|
2025-01-09 12:13:58 -06:00
|
|
|
response = view.create()
|
|
|
|
self.assertIsInstance(response, Response)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(response.content_type, "application/json")
|
|
|
|
self.assertEqual(
|
|
|
|
response.json_body, {"error": "RuntimeError"}
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
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)
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def test_customer_autocomplete(self):
|
|
|
|
model = self.app.model
|
2025-01-12 22:03:31 -06:00
|
|
|
handler = self.make_handler()
|
2025-01-09 12:13:58 -06:00
|
|
|
view = self.make_view()
|
2025-01-12 22:03:31 -06:00
|
|
|
view.batch_handler = handler
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# empty results by default
|
|
|
|
self.assertEqual(view.customer_autocomplete(), [])
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "GET", new={"term": "foo"}, create=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
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
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "GET", new={"term": "chuck"}, create=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.customer_autocomplete()
|
|
|
|
self.assertEqual(len(result), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
result[0],
|
|
|
|
{
|
|
|
|
"value": customer.uuid.hex,
|
|
|
|
"label": "Chuck Norris",
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# search for sally finds nothing
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "GET", new={"term": "sally"}, create=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.customer_autocomplete()
|
|
|
|
self.assertEqual(result, [])
|
|
|
|
|
2025-01-12 22:03:31 -06:00
|
|
|
# external lookup not implemented by default
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(handler, "use_local_customers", return_value=False):
|
|
|
|
with patch.object(
|
|
|
|
self.request, "GET", new={"term": "sally"}, create=True
|
|
|
|
):
|
2025-01-12 22:03:31 -06:00
|
|
|
self.assertRaises(NotImplementedError, view.customer_autocomplete)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def test_product_autocomplete(self):
|
|
|
|
model = self.app.model
|
2025-01-12 22:03:31 -06:00
|
|
|
handler = self.make_handler()
|
2025-01-09 12:13:58 -06:00
|
|
|
view = self.make_view()
|
2025-01-12 22:03:31 -06:00
|
|
|
view.batch_handler = handler
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# empty results by default
|
|
|
|
self.assertEqual(view.product_autocomplete(), [])
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "GET", new={"term": "foo"}, create=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
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
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request, "GET", new={"term": "vinegar"}, create=True
|
|
|
|
):
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.product_autocomplete()
|
|
|
|
self.assertEqual(len(result), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
result[0],
|
|
|
|
{
|
|
|
|
"value": product.uuid.hex,
|
|
|
|
"label": "Bragg's Vinegar",
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# search for brag finds product
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "GET", new={"term": "brag"}, create=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.product_autocomplete()
|
|
|
|
self.assertEqual(len(result), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
result[0],
|
|
|
|
{
|
|
|
|
"value": product.uuid.hex,
|
|
|
|
"label": "Bragg's Vinegar",
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# search for juice finds nothing
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "GET", new={"term": "juice"}, create=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.product_autocomplete()
|
|
|
|
self.assertEqual(result, [])
|
|
|
|
|
2025-01-12 22:03:31 -06:00
|
|
|
# external lookup not implemented by default
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(handler, "use_local_products", return_value=False):
|
|
|
|
with patch.object(
|
|
|
|
self.request, "GET", new={"term": "juice"}, create=True
|
|
|
|
):
|
2025-01-12 22:03:31 -06:00
|
|
|
self.assertRaises(NotImplementedError, view.product_autocomplete)
|
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
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()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(fields, ["description"])
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# but config can specify otherwise
|
2025-08-31 12:59:28 -05:00
|
|
|
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"
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
fields = view.get_pending_product_required_fields()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(fields, ["brand_name", "size", "unit_price_reg"])
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-30 21:45:10 -06:00
|
|
|
def test_get_dept_item_discounts(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-30 21:45:10 -06:00
|
|
|
|
|
|
|
# empty list by default
|
|
|
|
discounts = view.get_dept_item_discounts()
|
|
|
|
self.assertEqual(discounts, [])
|
|
|
|
|
|
|
|
# mock settings
|
2025-08-31 12:59:28 -05:00
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.5.name", "Bulk"
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session,
|
|
|
|
"sideshow.orders.departments.5.default_item_discount",
|
|
|
|
"15",
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.6.name", "Produce"
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.6.default_item_discount", "5"
|
|
|
|
)
|
2025-01-30 21:45:10 -06:00
|
|
|
discounts = view.get_dept_item_discounts()
|
|
|
|
self.assertEqual(len(discounts), 2)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
discounts[0],
|
|
|
|
{
|
|
|
|
"department_id": "5",
|
|
|
|
"department_name": "Bulk",
|
|
|
|
"default_item_discount": "15",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
discounts[1],
|
|
|
|
{
|
|
|
|
"department_id": "6",
|
|
|
|
"department_name": "Produce",
|
|
|
|
"default_item_discount": "5",
|
|
|
|
},
|
|
|
|
)
|
2025-01-30 21:45:10 -06:00
|
|
|
|
|
|
|
# invalid setting
|
2025-08-31 12:59:28 -05:00
|
|
|
self.app.save_setting(
|
|
|
|
self.session,
|
|
|
|
"sideshow.orders.departments.I.N.V.A.L.I.D.name",
|
|
|
|
"Bad News",
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session,
|
|
|
|
"sideshow.orders.departments.I.N.V.A.L.I.D.default_item_discount",
|
|
|
|
"42",
|
|
|
|
)
|
2025-01-30 21:45:10 -06:00
|
|
|
discounts = view.get_dept_item_discounts()
|
|
|
|
self.assertEqual(len(discounts), 2)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
discounts[0],
|
|
|
|
{
|
|
|
|
"department_id": "5",
|
|
|
|
"department_name": "Bulk",
|
|
|
|
"default_item_discount": "15",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
discounts[1],
|
|
|
|
{
|
|
|
|
"department_id": "6",
|
|
|
|
"department_name": "Produce",
|
|
|
|
"default_item_discount": "5",
|
|
|
|
},
|
|
|
|
)
|
2025-01-30 21:45:10 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
def test_get_context_customer(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders", "/orders/")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
2025-01-09 12:13:58 -06:00
|
|
|
view.batch_handler = handler
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# with external customer
|
2025-08-31 12:59:28 -05:00
|
|
|
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",
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.add(batch)
|
|
|
|
self.session.flush()
|
|
|
|
context = view.get_context_customer(batch)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"customer_is_known": True,
|
|
|
|
"customer_id": 42,
|
|
|
|
"customer_name": "Fred Flintstone",
|
|
|
|
"phone_number": "555-1234",
|
|
|
|
"email_address": "fred@mailinator.com",
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# with local customer
|
|
|
|
local = model.LocalCustomer(full_name="Betty Boop")
|
|
|
|
self.session.add(local)
|
2025-08-31 12:59:28 -05:00
|
|
|
batch = handler.make_batch(
|
|
|
|
self.session,
|
|
|
|
created_by=user,
|
|
|
|
local_customer=local,
|
|
|
|
customer_name="Betty Boop",
|
|
|
|
phone_number="555-8888",
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(batch)
|
|
|
|
self.session.flush()
|
|
|
|
context = view.get_context_customer(batch)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"customer_is_known": True,
|
|
|
|
"customer_id": local.uuid.hex,
|
|
|
|
"customer_name": "Betty Boop",
|
|
|
|
"phone_number": "555-8888",
|
|
|
|
"email_address": None,
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# with pending customer
|
|
|
|
batch = handler.make_batch(self.session, created_by=user)
|
|
|
|
self.session.add(batch)
|
2025-08-31 12:59:28 -05:00
|
|
|
handler.set_customer(
|
|
|
|
batch,
|
|
|
|
dict(
|
|
|
|
full_name="Fred Flintstone",
|
|
|
|
first_name="Fred",
|
|
|
|
last_name="Flintstone",
|
|
|
|
phone_number="555-1234",
|
|
|
|
email_address="fred@mailinator.com",
|
|
|
|
),
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
|
|
|
context = view.get_context_customer(batch)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"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",
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# 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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"customer_is_known": True, # nb. this is for UI default
|
|
|
|
"customer_id": None,
|
|
|
|
"customer_name": None,
|
|
|
|
"phone_number": None,
|
|
|
|
"email_address": None,
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_start_over(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders.create", "/orders/new")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
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):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders", "/orders/")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2025-01-27 20:33:14 -06:00
|
|
|
def test_set_store(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-27 20:33:14 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-27 20:33:14 -06:00
|
|
|
|
|
|
|
batch = view.get_current_batch()
|
|
|
|
self.assertIsNone(batch.store_id)
|
|
|
|
|
|
|
|
# store_id is required
|
|
|
|
result = view.set_store(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Must provide store_id"})
|
|
|
|
result = view.set_store(batch, {"store_id": ""})
|
|
|
|
self.assertEqual(result, {"error": "Must provide store_id"})
|
2025-01-27 20:33:14 -06:00
|
|
|
|
|
|
|
# store_id is set on batch
|
2025-08-31 12:59:28 -05:00
|
|
|
result = view.set_store(batch, {"store_id": "042"})
|
|
|
|
self.assertEqual(batch.store_id, "042")
|
|
|
|
self.assertIn("store_id", result)
|
|
|
|
self.assertEqual(result["store_id"], "042")
|
2025-01-27 20:33:14 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def test_assign_customer(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders.create", "/orders/new")
|
2025-01-09 12:13:58 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.add(user)
|
|
|
|
weirdal = model.LocalCustomer(full_name="Weird Al")
|
|
|
|
self.session.add(weirdal)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-09 12:13:58 -06:00
|
|
|
batch = view.get_current_batch()
|
|
|
|
|
|
|
|
# normal
|
|
|
|
self.assertIsNone(batch.local_customer)
|
|
|
|
self.assertIsNone(batch.pending_customer)
|
2025-08-31 12:59:28 -05:00
|
|
|
context = view.assign_customer(
|
|
|
|
batch, {"customer_id": weirdal.uuid.hex}
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.assertIsNone(batch.pending_customer)
|
|
|
|
self.assertIs(batch.local_customer, weirdal)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"customer_is_known": True,
|
|
|
|
"customer_id": weirdal.uuid.hex,
|
|
|
|
"customer_name": "Weird Al",
|
|
|
|
"phone_number": None,
|
|
|
|
"email_address": None,
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# missing customer_id
|
|
|
|
context = view.assign_customer(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(context, {"error": "Must provide customer_id"})
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
def test_unassign_customer(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders.create", "/orders/new")
|
2025-01-09 12:13:58 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-09 12:13:58 -06:00
|
|
|
batch = view.get_current_batch()
|
2025-08-31 12:59:28 -05:00
|
|
|
view.set_pending_customer(
|
|
|
|
batch, {"first_name": "Jack", "last_name": "Black"}
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# normal
|
|
|
|
self.assertIsNone(batch.local_customer)
|
|
|
|
self.assertIsNotNone(batch.pending_customer)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(batch.customer_name, "Jack Black")
|
2025-01-09 12:13:58 -06:00
|
|
|
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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"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,
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
def test_set_pending_customer(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders.create", "/orders/new")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
data = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"first_name": "Fred",
|
|
|
|
"last_name": "Flintstone",
|
|
|
|
"phone_number": "555-1234",
|
|
|
|
"email_address": "fred@mailinator.com",
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
context,
|
|
|
|
{
|
|
|
|
"store_id": None,
|
|
|
|
"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",
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def test_get_product_info(self):
|
|
|
|
model = self.app.model
|
|
|
|
handler = self.make_handler()
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
local = model.LocalProduct(
|
|
|
|
scancode="07430500132",
|
|
|
|
brand_name="Bragg",
|
|
|
|
description="Vinegar",
|
|
|
|
size="32oz",
|
|
|
|
case_size=12,
|
|
|
|
unit_price_reg=decimal.Decimal("5.99"),
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.add(local)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-09 12:13:58 -06:00
|
|
|
batch = view.get_current_batch()
|
|
|
|
|
|
|
|
# typical, for local product
|
2025-08-31 12:59:28 -05:00
|
|
|
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)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# error if no product_id
|
|
|
|
context = view.get_product_info(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(context, {"error": "Must specify a product ID"})
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-12 22:03:31 -06:00
|
|
|
# error if product not found
|
|
|
|
mock_uuid = self.app.make_true_uuid()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertRaises(
|
|
|
|
ValueError,
|
|
|
|
view.get_product_info,
|
|
|
|
batch,
|
|
|
|
{"product_id": mock_uuid.hex},
|
|
|
|
)
|
2025-01-12 22:03:31 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
handler, "use_local_products", return_value=False
|
|
|
|
):
|
2025-01-12 22:03:31 -06:00
|
|
|
|
|
|
|
# external lookup not implemented by default
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertRaises(
|
|
|
|
NotImplementedError,
|
|
|
|
view.get_product_info,
|
|
|
|
batch,
|
|
|
|
{"product_id": "42"},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-12 22:03:31 -06:00
|
|
|
# external lookup may return its own error
|
2025-08-31 12:59:28 -05:00
|
|
|
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"}
|
|
|
|
)
|
2025-01-12 22:03:31 -06:00
|
|
|
|
2025-02-01 19:39:02 -06:00
|
|
|
def test_get_past_products(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
handler = view.batch_handler
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-02-01 19:39:02 -06:00
|
|
|
self.session.add(user)
|
|
|
|
batch = handler.make_batch(self.session, created_by=user)
|
|
|
|
self.session.add(batch)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# (nb. this all assumes local customers and products)
|
|
|
|
|
|
|
|
# error if no customer
|
|
|
|
self.assertRaises(ValueError, view.get_past_products, batch, {})
|
|
|
|
|
|
|
|
# empty history for customer
|
2025-08-31 12:59:28 -05:00
|
|
|
customer = model.LocalCustomer(full_name="Fred Flintstone")
|
2025-02-01 19:39:02 -06:00
|
|
|
batch.local_customer = customer
|
|
|
|
self.session.flush()
|
|
|
|
products = view.get_past_products(batch, {})
|
|
|
|
self.assertEqual(len(products), 0)
|
|
|
|
|
|
|
|
# mock historical order
|
|
|
|
order = model.Order(order_id=42, local_customer=customer, created_by=user)
|
2025-08-31 12:59:28 -05:00
|
|
|
product = model.LocalProduct(
|
|
|
|
scancode="07430500132",
|
|
|
|
description="Vinegar",
|
|
|
|
unit_price_reg=5.99,
|
|
|
|
case_size=12,
|
|
|
|
)
|
|
|
|
item = model.OrderItem(
|
|
|
|
local_product=product,
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_READY,
|
|
|
|
)
|
2025-02-01 19:39:02 -06:00
|
|
|
order.items.append(item)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
self.session.refresh(product)
|
|
|
|
|
|
|
|
# that should now be returned
|
|
|
|
products = view.get_past_products(batch, {})
|
|
|
|
self.assertEqual(len(products), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(products[0]["product_id"], product.uuid.hex)
|
|
|
|
self.assertEqual(products[0]["scancode"], "07430500132")
|
|
|
|
self.assertEqual(products[0]["description"], "Vinegar")
|
2025-02-01 19:39:02 -06:00
|
|
|
# nb. this is a float, since result is JSON-safe
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(products[0]["case_price_quoted"], 71.88)
|
|
|
|
self.assertEqual(products[0]["case_price_quoted_display"], "$71.88")
|
2025-02-01 19:39:02 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
def test_add_item(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
2025-08-31 12:59:28 -05:00
|
|
|
self.config.setdefault("sideshow.orders.allow_item_discounts", "true")
|
2025-01-06 17:03:41 -06:00
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
data = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"product_info": {
|
|
|
|
"scancode": "07430500132",
|
|
|
|
"brand_name": "Bragg",
|
|
|
|
"description": "Vinegar",
|
|
|
|
"size": "32oz",
|
|
|
|
"unit_price_reg": 5.99,
|
2025-01-06 17:03:41 -06:00
|
|
|
},
|
2025-08-31 12:59:28 -05:00
|
|
|
"order_qty": 1,
|
|
|
|
"order_uom": enum.ORDER_UOM_UNIT,
|
|
|
|
"discount_percent": 10,
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
batch = view.get_current_batch()
|
|
|
|
self.session.flush()
|
|
|
|
self.assertEqual(len(batch.rows), 0)
|
|
|
|
|
|
|
|
# normal pending product
|
|
|
|
result = view.add_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("batch", result)
|
|
|
|
self.assertIn("row", result)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
|
|
|
self.assertEqual(len(batch.rows), 1)
|
|
|
|
row = batch.rows[0]
|
|
|
|
self.assertIsInstance(row.pending_product, model.PendingProduct)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# external product not yet supported
|
2025-08-31 12:59:28 -05:00
|
|
|
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
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_update_item(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
2025-08-31 12:59:28 -05:00
|
|
|
self.config.setdefault("sideshow.orders.allow_item_discounts", "true")
|
2025-01-06 17:03:41 -06:00
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
data = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"product_info": {
|
|
|
|
"scancode": "07430500132",
|
|
|
|
"brand_name": "Bragg",
|
|
|
|
"description": "Vinegar",
|
|
|
|
"size": "32oz",
|
|
|
|
"unit_price_reg": 5.99,
|
|
|
|
"case_size": 12,
|
2025-01-06 17:03:41 -06:00
|
|
|
},
|
2025-08-31 12:59:28 -05:00
|
|
|
"order_qty": 1,
|
|
|
|
"order_uom": enum.ORDER_UOM_CASE,
|
|
|
|
"discount_percent": 15,
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(row.unit_price_quoted, decimal.Decimal("5.99"))
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# missing row uuid
|
|
|
|
result = view.update_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Must specify row UUID"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# row not found
|
|
|
|
with patch.dict(data, uuid=self.app.make_true_uuid()):
|
|
|
|
result = view.update_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Row not found"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# 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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Row is for wrong batch"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# true product not yet supported
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# update row, pending product
|
2025-01-09 12:13:58 -06:00
|
|
|
with patch.dict(data, uuid=row.uuid, order_qty=2):
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.dict(data["product_info"], scancode="07430500116"):
|
|
|
|
self.assertEqual(row.product_scancode, "07430500132")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.assertEqual(row.order_qty, 1)
|
|
|
|
result = view.update_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(sorted(result), ["batch", "row"])
|
|
|
|
self.assertEqual(row.product_scancode, "07430500116")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.assertEqual(row.order_qty, 2)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
row.pending_product.scancode, "07430500116"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
result["row"]["product_scancode"], "07430500116"
|
|
|
|
)
|
|
|
|
self.assertEqual(result["row"]["order_qty"], 2)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_delete_item(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
data = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"product_info": {
|
|
|
|
"scancode": "07430500132",
|
|
|
|
"brand_name": "Bragg",
|
|
|
|
"description": "Vinegar",
|
|
|
|
"size": "32oz",
|
|
|
|
"unit_price_reg": 5.99,
|
|
|
|
"case_size": 12,
|
2025-01-06 17:03:41 -06:00
|
|
|
},
|
2025-08-31 12:59:28 -05:00
|
|
|
"order_qty": 1,
|
|
|
|
"order_uom": enum.ORDER_UOM_CASE,
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(row.unit_price_quoted, decimal.Decimal("5.99"))
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# missing row uuid
|
|
|
|
result = view.delete_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Must specify a row UUID"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# row not found
|
|
|
|
with patch.dict(data, uuid=self.app.make_true_uuid()):
|
|
|
|
result = view.delete_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Row not found"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# 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)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Row is for wrong batch"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# row is deleted
|
2025-08-31 12:59:28 -05:00
|
|
|
data["uuid"] = row.uuid
|
2025-01-06 17:03:41 -06:00
|
|
|
self.assertEqual(len(batch.rows), 1)
|
|
|
|
self.assertEqual(batch.row_count, 1)
|
|
|
|
result = view.delete_item(batch, data)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(sorted(result), ["batch"])
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.refresh(batch)
|
|
|
|
self.assertEqual(len(batch.rows), 0)
|
|
|
|
self.assertEqual(batch.row_count, 0)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def test_submit_order(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders.view", "/orders/{uuid}")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
data = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"product_info": {
|
|
|
|
"scancode": "07430500132",
|
|
|
|
"brand_name": "Bragg",
|
|
|
|
"description": "Vinegar",
|
|
|
|
"size": "32oz",
|
|
|
|
"unit_price_reg": 5.99,
|
|
|
|
"case_size": 12,
|
2025-01-06 17:03:41 -06:00
|
|
|
},
|
2025-08-31 12:59:28 -05:00
|
|
|
"order_qty": 1,
|
|
|
|
"order_uom": enum.ORDER_UOM_CASE,
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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):
|
2025-01-06 17:03:41 -06:00
|
|
|
batch = view.get_current_batch()
|
|
|
|
self.assertEqual(len(batch.rows), 0)
|
|
|
|
|
|
|
|
# add row w/ pending product
|
|
|
|
view.add_item(batch, data)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.assertEqual(len(batch.rows), 1)
|
2025-01-06 17:03:41 -06:00
|
|
|
row = batch.rows[0]
|
|
|
|
self.assertIsInstance(row.pending_product, model.PendingProduct)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(row.unit_price_quoted, decimal.Decimal("5.99"))
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# execute not allowed yet (no customer)
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.submit_order(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(result, {"error": "Must assign the customer"})
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# execute not allowed yet (no phone number)
|
2025-08-31 12:59:28 -05:00
|
|
|
view.set_pending_customer(batch, {"full_name": "John Doe"})
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.submit_order(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
result, {"error": "Customer phone number is required"}
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
# submit/execute ok
|
2025-08-31 12:59:28 -05:00
|
|
|
view.set_pending_customer(
|
|
|
|
batch, {"full_name": "John Doe", "phone_number": "555-1234"}
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.submit_order(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(sorted(result), ["next_url"])
|
|
|
|
self.assertIn("/orders/", result["next_url"])
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# error (already executed)
|
2025-01-09 12:13:58 -06:00
|
|
|
result = view.submit_order(batch, {})
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
result,
|
|
|
|
{
|
|
|
|
"error": f"ValueError: batch has already been executed: {batch}",
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_normalize_batch(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
batch = handler.make_batch(self.session, created_by=user)
|
|
|
|
self.session.add(batch)
|
|
|
|
pending = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"scancode": "07430500132",
|
|
|
|
"brand_name": "Bragg",
|
|
|
|
"description": "Vinegar",
|
|
|
|
"size": "32oz",
|
|
|
|
"unit_price_reg": 5.99,
|
|
|
|
"case_size": 12,
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
2025-01-09 12:13:58 -06:00
|
|
|
row = handler.add_item(batch, pending, 1, enum.ORDER_UOM_CASE)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.commit()
|
|
|
|
|
|
|
|
data = view.normalize_batch(batch)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
data,
|
|
|
|
{
|
|
|
|
"uuid": batch.uuid.hex,
|
|
|
|
"total_price": "71.880",
|
|
|
|
"total_price_display": "$71.88",
|
|
|
|
"status_code": None,
|
|
|
|
"status_text": None,
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_normalize_row(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
2025-01-09 12:13:58 -06:00
|
|
|
view.batch_handler = handler
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
batch = handler.make_batch(self.session, created_by=user)
|
|
|
|
self.session.add(batch)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# add 1st row w/ pending product
|
2025-01-06 17:03:41 -06:00
|
|
|
pending = {
|
2025-08-31 12:59:28 -05:00
|
|
|
"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",
|
2025-01-06 17:03:41 -06:00
|
|
|
}
|
2025-01-09 12:13:58 -06:00
|
|
|
row1 = handler.add_item(batch, pending, 2, enum.ORDER_UOM_CASE)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# typical, pending product
|
|
|
|
data = view.normalize_row(row1)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.assertIsInstance(data, dict)
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# the next few tests will morph 1st row..
|
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
# unknown case size
|
2025-01-09 12:13:58 -06:00
|
|
|
row1.pending_product.case_size = None
|
|
|
|
handler.refresh_row(row1)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
2025-01-09 12:13:58 -06:00
|
|
|
data = view.normalize_row(row1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIsNone(data["case_size"])
|
|
|
|
self.assertEqual(data["order_qty_display"], "2 Cases (× ?? = ?? Units)")
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# order by unit
|
2025-01-09 12:13:58 -06:00
|
|
|
row1.order_uom = enum.ORDER_UOM_UNIT
|
|
|
|
handler.refresh_row(row1)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
2025-01-09 12:13:58 -06:00
|
|
|
data = view.normalize_row(row1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(data["order_uom"], enum.ORDER_UOM_UNIT)
|
|
|
|
self.assertEqual(data["order_qty_display"], "2 Units")
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# item on sale
|
2025-01-09 12:13:58 -06:00
|
|
|
row1.pending_product.case_size = 12
|
2025-08-31 12:59:28 -05:00
|
|
|
row1.unit_price_sale = decimal.Decimal("5.19")
|
2025-01-09 12:13:58 -06:00
|
|
|
row1.sale_ends = datetime.datetime(2099, 1, 5, 20, 32)
|
|
|
|
handler.refresh_row(row1)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
2025-01-09 12:13:58 -06:00
|
|
|
data = view.normalize_row(row1)
|
2025-08-31 12:59:28 -05:00
|
|
|
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")
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# add 2nd row w/ local product
|
2025-08-31 12:59:28 -05:00
|
|
|
local = model.LocalProduct(
|
|
|
|
brand_name="Lay's",
|
|
|
|
description="Potato Chips",
|
|
|
|
vendor_name="Acme Distribution",
|
|
|
|
unit_price_reg=3.29,
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
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)
|
2025-08-31 12:59:28 -05:00
|
|
|
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)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# the next few tests will morph 2nd row..
|
|
|
|
|
2025-01-12 22:03:31 -06:00
|
|
|
def refresh_external(row):
|
2025-08-31 12:59:28 -05:00
|
|
|
row.product_scancode = "012345"
|
|
|
|
row.product_brand = "Acme"
|
|
|
|
row.product_description = "Bricks"
|
|
|
|
row.product_size = "1 ton"
|
2025-01-12 22:03:31 -06:00
|
|
|
row.product_weighed = True
|
|
|
|
row.department_id = 1
|
|
|
|
row.department_name = "Bricks & Mortar"
|
|
|
|
row.special_order = False
|
2025-08-31 12:59:28 -05:00
|
|
|
row.vendor_name = "Acme Distributors"
|
|
|
|
row.vendor_item_code = "1234"
|
2025-01-12 22:03:31 -06:00
|
|
|
row.case_size = None
|
2025-08-31 12:59:28 -05:00
|
|
|
row.unit_cost = decimal.Decimal("599.99")
|
|
|
|
row.unit_price_reg = decimal.Decimal("999.99")
|
2025-01-12 22:03:31 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# typical, external product
|
2025-08-31 12:59:28 -05:00
|
|
|
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)
|
2025-01-12 22:03:31 -06:00
|
|
|
data = view.normalize_row(row2)
|
2025-08-31 12:59:28 -05:00
|
|
|
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.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")
|
|
|
|
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)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
def test_get_instance_title(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
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()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.commit()
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# viewing (no customer)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
2025-01-06 17:03:41 -06:00
|
|
|
form = view.make_form(model_instance=order)
|
|
|
|
# nb. this is to avoid include/exclude ambiguity
|
2025-08-31 12:59:28 -05:00
|
|
|
form.remove("items")
|
2025-01-27 20:33:14 -06:00
|
|
|
# nb. store_id gets hidden by default
|
2025-08-31 12:59:28 -05:00
|
|
|
form.append("store_id")
|
|
|
|
self.assertIn("store_id", form)
|
2025-01-06 17:03:41 -06:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertNotIn("store_id", form)
|
2025-01-06 17:03:41 -06:00
|
|
|
schema = form.get_schema()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("pending_customer", form)
|
|
|
|
self.assertIsInstance(schema["total_price"].typ, WuttaMoney)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# assign local customer
|
2025-08-31 12:59:28 -05:00
|
|
|
local = model.LocalCustomer(
|
|
|
|
first_name="Jack", last_name="Black", phone_number="555-1234"
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.session.add(local)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-01-27 20:33:14 -06:00
|
|
|
# nb. from now on we include store_id
|
2025-08-31 12:59:28 -05:00
|
|
|
self.config.setdefault("sideshow.orders.expose_store_id", "true")
|
2025-01-27 20:33:14 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# viewing (local customer)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
|
|
|
with patch.object(order, "local_customer", new=local):
|
2025-01-13 18:56:41 -06:00
|
|
|
form = view.make_form(model_instance=order)
|
|
|
|
# nb. this is to avoid include/exclude ambiguity
|
2025-08-31 12:59:28 -05:00
|
|
|
form.remove("items")
|
2025-01-27 20:33:14 -06:00
|
|
|
# nb. store_id will now remain
|
2025-08-31 12:59:28 -05:00
|
|
|
form.append("store_id")
|
|
|
|
self.assertIn("store_id", form)
|
2025-01-13 18:56:41 -06:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("store_id", form)
|
|
|
|
self.assertNotIn("pending_customer", form)
|
2025-01-13 18:56:41 -06:00
|
|
|
schema = form.get_schema()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIsInstance(schema["total_price"].typ, WuttaMoney)
|
2025-01-13 18:56:41 -06:00
|
|
|
|
|
|
|
# local customer is hidden if missing when customer_id is set
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
|
|
|
with patch.object(order, "customer_id", new="42"):
|
2025-01-13 18:56:41 -06:00
|
|
|
form = view.make_form(model_instance=order)
|
|
|
|
# nb. this is to avoid include/exclude ambiguity
|
2025-08-31 12:59:28 -05:00
|
|
|
form.remove("items")
|
2025-01-13 18:56:41 -06:00
|
|
|
view.configure_form(form)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertNotIn("local_customer", form)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_get_xref_buttons(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("neworder_batches.view", "/batch/neworder/{uuid}")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
handler = NewOrderBatchHandler(self.config)
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# nb. this requires perm to view batch
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# order has no batch, so no buttons
|
|
|
|
buttons = view.get_xref_buttons(order)
|
|
|
|
self.assertEqual(buttons, [])
|
|
|
|
|
|
|
|
# mock up a batch to get a button
|
2025-08-31 12:59:28 -05:00
|
|
|
batch = handler.make_batch(
|
|
|
|
self.session,
|
|
|
|
id=order.order_id,
|
|
|
|
created_by=user,
|
|
|
|
executed=datetime.datetime.now(),
|
|
|
|
executed_by=user,
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
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()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-06 17:03:41 -06:00
|
|
|
query = view.get_row_grid_data(order)
|
|
|
|
self.assertIsInstance(query, orm.Query)
|
|
|
|
items = query.all()
|
|
|
|
self.assertEqual(len(items), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(items[0].product_scancode, "07430500132")
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_configure_row_grid(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-06 17:03:41 -06:00
|
|
|
grid = view.make_grid(model_class=model.OrderItem, data=order.items)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertNotIn("product_scancode", grid.linked_columns)
|
2025-01-06 17:03:41 -06:00
|
|
|
view.configure_row_grid(grid)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("product_scancode", grid.linked_columns)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-15 21:49:17 -06:00
|
|
|
def test_row_grid_row_class(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
|
|
# typical
|
|
|
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertIsNone(view.row_grid_row_class(item, {}, 1))
|
|
|
|
|
|
|
|
# warning
|
|
|
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_CANCELED)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(view.row_grid_row_class(item, {}, 1), "has-background-warning")
|
2025-01-15 21:49:17 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
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")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
result, enum.ORDER_ITEM_STATUS[enum.ORDER_ITEM_STATUS_INITIATED]
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
def test_get_row_action_url_view(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items.view", "/order-items/{uuid}")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
2025-08-31 12:59:28 -05:00
|
|
|
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,
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
order.items.append(item)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
url = view.get_row_action_url_view(item, 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn(f"/order-items/{item.uuid}", url)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
def test_configure(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("home", "/")
|
|
|
|
self.pyramid_config.add_route("login", "/auth/login")
|
|
|
|
self.pyramid_config.add_route("orders", "/orders/")
|
2025-01-09 12:13:58 -06:00
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.5.name", "Bulk"
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.5.default_item_discount", "15"
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.6.name", "Produce"
|
|
|
|
)
|
|
|
|
self.app.save_setting(
|
|
|
|
self.session, "sideshow.orders.departments.6.default_item_discount", "5"
|
|
|
|
)
|
2025-01-30 21:45:10 -06:00
|
|
|
self.session.commit()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-09 12:13:58 -06:00
|
|
|
with patch.multiple(self.config, usedb=True, preferdb=True):
|
|
|
|
|
|
|
|
# sanity check
|
2025-08-31 12:59:28 -05:00
|
|
|
allowed = self.config.get_bool(
|
|
|
|
"sideshow.orders.allow_unknown_products", session=self.session
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.assertIsNone(allowed)
|
2025-01-30 21:45:10 -06:00
|
|
|
self.assertEqual(self.session.query(model.Setting).count(), 4)
|
|
|
|
discounts = view.get_dept_item_discounts()
|
|
|
|
self.assertEqual(len(discounts), 2)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
discounts[0],
|
|
|
|
{
|
|
|
|
"department_id": "5",
|
|
|
|
"department_name": "Bulk",
|
|
|
|
"default_item_discount": "15",
|
|
|
|
},
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
discounts[1],
|
|
|
|
{
|
|
|
|
"department_id": "6",
|
|
|
|
"department_name": "Produce",
|
|
|
|
"default_item_discount": "5",
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# fetch initial page
|
|
|
|
response = view.configure()
|
|
|
|
self.assertIsInstance(response, Response)
|
|
|
|
self.assertNotIsInstance(response, HTTPFound)
|
|
|
|
self.session.flush()
|
2025-08-31 12:59:28 -05:00
|
|
|
allowed = self.config.get_bool(
|
|
|
|
"sideshow.orders.allow_unknown_products", session=self.session
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.assertIsNone(allowed)
|
2025-01-30 21:45:10 -06:00
|
|
|
self.assertEqual(self.session.query(model.Setting).count(), 4)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# post new settings
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(
|
|
|
|
self.request,
|
|
|
|
create=True,
|
|
|
|
method="POST",
|
|
|
|
POST={
|
|
|
|
"sideshow.orders.allow_unknown_products": "true",
|
|
|
|
"dept_item_discounts": json.dumps(
|
|
|
|
[
|
|
|
|
{
|
|
|
|
"department_id": "5",
|
|
|
|
"department_name": "Grocery",
|
|
|
|
"default_item_discount": 10,
|
|
|
|
}
|
|
|
|
]
|
|
|
|
),
|
|
|
|
},
|
|
|
|
):
|
2025-01-09 12:13:58 -06:00
|
|
|
response = view.configure()
|
|
|
|
self.assertIsInstance(response, HTTPFound)
|
|
|
|
self.session.flush()
|
2025-08-31 12:59:28 -05:00
|
|
|
allowed = self.config.get_bool(
|
|
|
|
"sideshow.orders.allow_unknown_products", session=self.session
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
self.assertTrue(allowed)
|
|
|
|
self.assertTrue(self.session.query(model.Setting).count() > 1)
|
2025-01-30 21:45:10 -06:00
|
|
|
discounts = view.get_dept_item_discounts()
|
|
|
|
self.assertEqual(len(discounts), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
discounts[0],
|
|
|
|
{
|
|
|
|
"department_id": "5",
|
|
|
|
"department_name": "Grocery",
|
|
|
|
"default_item_discount": "10",
|
|
|
|
},
|
|
|
|
)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
class OrderItemViewTestMixin:
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_get_fallback_templates(self):
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
templates = view.get_fallback_templates("view")
|
|
|
|
self.assertEqual(templates, ["/order-items/view.mako", "/master/view.mako"])
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_common_get_query(self):
|
2025-01-06 17:03:41 -06:00
|
|
|
view = self.make_view()
|
|
|
|
query = view.get_query(session=self.session)
|
|
|
|
self.assertIsInstance(query, orm.Query)
|
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_configure_grid(self):
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
2025-01-27 20:33:14 -06:00
|
|
|
|
|
|
|
# store_id is removed by default
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
2025-08-31 12:59:28 -05:00
|
|
|
grid.append("store_id")
|
|
|
|
self.assertIn("store_id", grid.columns)
|
2025-01-27 20:33:14 -06:00
|
|
|
view.configure_grid(grid)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertNotIn("store_id", grid.columns)
|
2025-01-27 20:33:14 -06:00
|
|
|
|
|
|
|
# store_id is shown if configured
|
2025-08-31 12:59:28 -05:00
|
|
|
self.config.setdefault("sideshow.orders.expose_store_id", "true")
|
2025-01-06 17:03:41 -06:00
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
2025-08-31 12:59:28 -05:00
|
|
|
grid.append("store_id")
|
|
|
|
self.assertIn("store_id", grid.columns)
|
2025-01-06 17:03:41 -06:00
|
|
|
view.configure_grid(grid)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("store_id", grid.columns)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-27 20:33:14 -06:00
|
|
|
def test_common_render_order_attr(self):
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
order = model.Order(order_id=42)
|
|
|
|
item = model.OrderItem()
|
|
|
|
order.items.append(item)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(view.render_order_attr(item, "order_id", None), 42)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_render_status_code(self):
|
2025-01-06 17:03:41 -06:00
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED),
|
|
|
|
"initiated",
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_grid_row_class(self):
|
2025-01-09 12:13:58 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-01-15 21:49:17 -06:00
|
|
|
# typical
|
|
|
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertIsNone(view.grid_row_class(item, {}, 1))
|
|
|
|
|
|
|
|
# warning
|
|
|
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_CANCELED)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(view.grid_row_class(item, {}, 1), "has-background-warning")
|
2025-01-09 12:13:58 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_configure_form(self):
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
|
|
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_INITIATED)
|
|
|
|
|
2025-01-09 12:13:58 -06:00
|
|
|
# viewing, w/ pending product
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
2025-01-09 12:13:58 -06:00
|
|
|
form = view.make_form(model_instance=item)
|
|
|
|
view.configure_form(form)
|
|
|
|
schema = form.get_schema()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIsInstance(schema["order"].typ, OrderRef)
|
|
|
|
self.assertIn("pending_product", form)
|
|
|
|
self.assertIsInstance(schema["pending_product"].typ, PendingProductRef)
|
2025-01-09 12:13:58 -06:00
|
|
|
|
|
|
|
# viewing, w/ local product
|
|
|
|
local = model.LocalProduct()
|
|
|
|
item.local_product = local
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "viewing", new=True):
|
2025-01-06 17:03:41 -06:00
|
|
|
form = view.make_form(model_instance=item)
|
|
|
|
view.configure_form(form)
|
|
|
|
schema = form.get_schema()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIsInstance(schema["order"].typ, OrderRef)
|
|
|
|
self.assertNotIn("pending_product", form)
|
2025-01-06 17:03:41 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_get_template_context(self):
|
2025-01-15 16:57:28 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
|
|
order = model.Order()
|
|
|
|
item = model.OrderItem(order_qty=2, order_uom=enum.ORDER_UOM_CASE, case_size=8)
|
|
|
|
order.items.append(item)
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
|
|
|
with patch.object(view, "viewing", new=True):
|
2025-01-15 19:25:45 -06:00
|
|
|
form = view.make_model_form(model_instance=item)
|
2025-08-31 12:59:28 -05:00
|
|
|
context = view.get_template_context({"instance": item, "form": form})
|
|
|
|
self.assertIn("item", context)
|
|
|
|
self.assertIs(context["item"], item)
|
|
|
|
self.assertIn("order", context)
|
|
|
|
self.assertIs(context["order"], order)
|
|
|
|
self.assertIn("order_qty_uom_text", context)
|
|
|
|
self.assertEqual(
|
|
|
|
context["order_qty_uom_text"], "2 Cases (× 8 = 16 Units)"
|
|
|
|
)
|
2025-01-15 16:57:28 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_render_event_note(self):
|
2025-01-15 21:49:17 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
|
|
|
# typical
|
2025-08-31 12:59:28 -05:00
|
|
|
event = model.OrderItemEvent(
|
|
|
|
type_code=enum.ORDER_ITEM_EVENT_READY, note="testing"
|
|
|
|
)
|
|
|
|
result = view.render_event_note(event, "note", "testing")
|
|
|
|
self.assertEqual(result, "testing")
|
2025-01-15 21:49:17 -06:00
|
|
|
|
|
|
|
# user note
|
2025-08-31 12:59:28 -05:00
|
|
|
event = model.OrderItemEvent(
|
|
|
|
type_code=enum.ORDER_ITEM_EVENT_NOTE_ADDED, note="testing2"
|
|
|
|
)
|
|
|
|
result = view.render_event_note(event, "note", "testing2")
|
|
|
|
self.assertNotEqual(result, "testing2")
|
|
|
|
self.assertIn("<span", result)
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertIn('class="has-background-info-light"', result)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("testing2", result)
|
2025-01-15 21:49:17 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_get_xref_buttons(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("orders.view", "/orders/{uuid}")
|
2025-01-06 17:03:41 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-06 17:03:41 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
2025-08-31 12:59:28 -05:00
|
|
|
item = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_INITIATED,
|
|
|
|
)
|
2025-01-06 17:03:41 -06:00
|
|
|
order.items.append(item)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# nb. this requires perms
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-01-06 17:03:41 -06:00
|
|
|
|
|
|
|
# one button by default
|
|
|
|
buttons = view.get_xref_buttons(item)
|
|
|
|
self.assertEqual(len(buttons), 1)
|
|
|
|
self.assertIn("View the Order", buttons[0])
|
2025-01-15 21:49:17 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_add_note(self):
|
2025-01-15 21:49:17 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route(
|
|
|
|
f"{view.get_route_prefix()}.view", f"{view.get_url_prefix()}/{{uuid}}"
|
|
|
|
)
|
2025-01-15 21:49:17 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-15 21:49:17 -06:00
|
|
|
self.session.add(user)
|
|
|
|
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
2025-08-31 12:59:28 -05:00
|
|
|
item = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_INITIATED,
|
|
|
|
)
|
2025-01-15 21:49:17 -06:00
|
|
|
order.items.append(item)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
|
|
|
with patch.object(self.request, "matchdict", new={"uuid": item.uuid}):
|
|
|
|
with patch.object(self.request, "POST", new={"note": "testing"}):
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 0)
|
|
|
|
result = view.add_note()
|
|
|
|
self.assertEqual(len(item.events), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item.events[0].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
|
|
|
self.assertEqual(item.events[0].note, "testing")
|
2025-01-15 21:49:17 -06:00
|
|
|
|
2025-01-22 21:32:15 -06:00
|
|
|
def test_common_change_status(self):
|
2025-01-15 21:49:17 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route(
|
|
|
|
f"{view.get_route_prefix()}.view", f"{view.get_url_prefix()}/{{uuid}}"
|
|
|
|
)
|
2025-01-15 21:49:17 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-15 21:49:17 -06:00
|
|
|
self.session.add(user)
|
|
|
|
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
|
|
|
self.session.add(order)
|
2025-08-31 12:59:28 -05:00
|
|
|
item = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_INITIATED,
|
|
|
|
)
|
2025-01-15 21:49:17 -06:00
|
|
|
order.items.append(item)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
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": item.uuid}):
|
2025-01-15 21:49:17 -06:00
|
|
|
|
|
|
|
# just status change, no note
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={"new_status": enum.ORDER_ITEM_STATUS_PLACED},
|
|
|
|
):
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 0)
|
|
|
|
result = view.change_status()
|
|
|
|
self.assertIsInstance(result, HTTPFound)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("error"))
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item.events[0].type_code,
|
|
|
|
enum.ORDER_ITEM_EVENT_STATUS_CHANGE,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item.events[0].note,
|
|
|
|
'status changed from "initiated" to "placed"',
|
|
|
|
)
|
2025-01-15 21:49:17 -06:00
|
|
|
|
|
|
|
# status change plus note
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"new_status": enum.ORDER_ITEM_STATUS_RECEIVED,
|
|
|
|
"note": "check it out",
|
|
|
|
},
|
|
|
|
):
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 1)
|
|
|
|
result = view.change_status()
|
|
|
|
self.assertIsInstance(result, HTTPFound)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("error"))
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 3)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item.events[0].type_code,
|
|
|
|
enum.ORDER_ITEM_EVENT_STATUS_CHANGE,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item.events[0].note,
|
|
|
|
'status changed from "initiated" to "placed"',
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item.events[1].type_code,
|
|
|
|
enum.ORDER_ITEM_EVENT_STATUS_CHANGE,
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item.events[1].note,
|
|
|
|
'status changed from "placed" to "received"',
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item.events[2].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(item.events[2].note, "check it out")
|
|
|
|
|
|
|
|
# invalid status
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request, "POST", new={"new_status": 23432143}
|
|
|
|
):
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 3)
|
|
|
|
result = view.change_status()
|
|
|
|
self.assertIsInstance(result, HTTPFound)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.peek_flash("error"))
|
2025-01-15 21:49:17 -06:00
|
|
|
self.assertEqual(len(item.events), 3)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
|
|
|
|
class TestOrderItemView(OrderItemViewTestMixin, WebTestCase):
|
|
|
|
|
|
|
|
def make_view(self):
|
|
|
|
return mod.OrderItemView(self.request)
|
|
|
|
|
|
|
|
def test_get_order_items(self):
|
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items", "/order-items/")
|
2025-01-22 21:32:15 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
|
|
|
order = model.Order(order_id=42, created_by=user)
|
2025-08-31 12:59:28 -05:00
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_READY,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
2025-08-31 12:59:28 -05:00
|
|
|
item2 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_READY,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item2)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# no items found
|
|
|
|
self.assertRaises(HTTPFound, view.get_order_items, None)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertRaises(HTTPFound, view.get_order_items, "")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.get_order_items, [])
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertRaises(HTTPFound, view.get_order_items, "invalid")
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# list of UUID
|
|
|
|
items = view.get_order_items([item1.uuid, item2.uuid])
|
|
|
|
self.assertEqual(len(items), 2)
|
|
|
|
self.assertIs(items[0], item1)
|
|
|
|
self.assertIs(items[1], item2)
|
|
|
|
|
|
|
|
# list of str
|
|
|
|
items = view.get_order_items([item1.uuid.hex, item2.uuid.hex])
|
|
|
|
self.assertEqual(len(items), 2)
|
|
|
|
self.assertIs(items[0], item1)
|
|
|
|
self.assertIs(items[1], item2)
|
|
|
|
|
|
|
|
# comma-delimited str
|
2025-08-31 12:59:28 -05:00
|
|
|
items = view.get_order_items(",".join([item1.uuid.hex, item2.uuid.hex]))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(len(items), 2)
|
|
|
|
self.assertIs(items[0], item1)
|
|
|
|
self.assertIs(items[1], item2)
|
|
|
|
|
|
|
|
|
|
|
|
class TestPlacementView(OrderItemViewTestMixin, WebTestCase):
|
|
|
|
|
|
|
|
def make_view(self):
|
|
|
|
return mod.PlacementView(self.request)
|
|
|
|
|
|
|
|
def test_configure_grid(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# nothing added without perms
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertFalse(grid.checkable)
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
|
|
|
|
# button added with perm
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertTrue(grid.checkable)
|
|
|
|
self.assertEqual(len(grid.tools), 1)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_placement", grid.tools)
|
|
|
|
tool = grid.tools["process_placement"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Order Placed", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_placement(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_placement", "/placement/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_READY,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
2025-08-31 12:59:28 -05:00
|
|
|
item2 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_READY,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item2)
|
2025-08-31 12:59:28 -05:00
|
|
|
item3 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_READY,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item3)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_placement"
|
|
|
|
) as process_placement:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_placement)
|
|
|
|
process_placement.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# two items are updated
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
|
|
|
self.assertEqual(len(item2.events), 0)
|
|
|
|
self.assertEqual(len(item3.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": ",".join([item1.uuid.hex, item2.uuid.hex]),
|
|
|
|
"vendor_name": "Acme Dist",
|
|
|
|
"po_number": "ACME123",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_placement()
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED)
|
|
|
|
self.assertEqual(item2.status_code, enum.ORDER_ITEM_STATUS_PLACED)
|
|
|
|
self.assertEqual(item3.status_code, enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertEqual(len(item1.events), 1)
|
|
|
|
self.assertEqual(len(item2.events), 1)
|
|
|
|
self.assertEqual(len(item3.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].note, "PO ACME123 for vendor Acme Dist"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item2.events[0].note, "PO ACME123 for vendor Acme Dist"
|
|
|
|
)
|
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
|
|
|
|
|
|
|
|
|
|
|
class TestReceivingView(OrderItemViewTestMixin, WebTestCase):
|
|
|
|
|
|
|
|
def make_view(self):
|
|
|
|
return mod.ReceivingView(self.request)
|
|
|
|
|
|
|
|
def test_configure_grid(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# nothing added without perms
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertFalse(grid.checkable)
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
|
|
|
|
# buttons added with perm
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertEqual(len(grid.tools), 2)
|
|
|
|
self.assertTrue(grid.checkable)
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_receiving", grid.tools)
|
|
|
|
tool = grid.tools["process_receiving"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Received", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_reorder", grid.tools)
|
|
|
|
tool = grid.tools["process_reorder"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Re-Order", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_receiving(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_receiving", "/receiving/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_PLACED,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_receiving"
|
|
|
|
) as process_receiving:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_receiving)
|
|
|
|
process_receiving.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# all info provided
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": item1.uuid.hex,
|
|
|
|
"vendor_name": "Acme Dist",
|
|
|
|
"invoice_number": "INV123",
|
|
|
|
"po_number": "123",
|
|
|
|
"note": "extra note",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_receiving()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED)
|
|
|
|
self.assertEqual(len(item1.events), 2)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].note,
|
|
|
|
"invoice INV123 (PO 123) from vendor Acme Dist",
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].type_code, enum.ORDER_ITEM_EVENT_RECEIVED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(item1.events[1].note, "extra note")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_reorder(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_receiving", "/receiving/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_PLACED,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_reorder"
|
|
|
|
) as process_reorder:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_reorder)
|
|
|
|
process_reorder.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# all info provided
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_PLACED)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": item1.uuid.hex,
|
|
|
|
"note": "extra note",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_reorder()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_READY)
|
|
|
|
self.assertEqual(len(item1.events), 2)
|
|
|
|
self.assertIsNone(item1.events[0].note)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].type_code, enum.ORDER_ITEM_EVENT_REORDER
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(item1.events[1].note, "extra note")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
|
|
|
|
class TestContactView(OrderItemViewTestMixin, WebTestCase):
|
|
|
|
|
|
|
|
def make_view(self):
|
|
|
|
return mod.ContactView(self.request)
|
|
|
|
|
|
|
|
def test_configure_grid(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# nothing added without perms
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertFalse(grid.checkable)
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
|
|
|
|
# buttons added with perm
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertEqual(len(grid.tools), 2)
|
|
|
|
self.assertTrue(grid.checkable)
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_contact_success", grid.tools)
|
|
|
|
tool = grid.tools["process_contact_success"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Contact Success", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_contact_failure", grid.tools)
|
|
|
|
tool = grid.tools["process_contact_failure"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Contact Failure", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_contact_success(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_contact", "/contact/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_RECEIVED,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_contact_success"
|
|
|
|
) as process_contact_success:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_contact_success)
|
|
|
|
process_contact_success.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# all info provided
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": item1.uuid.hex,
|
|
|
|
"note": "extra note",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_contact_success()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED)
|
|
|
|
self.assertEqual(len(item1.events), 2)
|
|
|
|
self.assertIsNone(item1.events[0].note)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACTED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(item1.events[1].note, "extra note")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_contact_failure(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_contact", "/contact/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_RECEIVED,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_contact_failure"
|
|
|
|
) as process_contact_failure:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_contact_failure)
|
|
|
|
process_contact_failure.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# all info provided
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RECEIVED)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": item1.uuid.hex,
|
|
|
|
"note": "extra note",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_contact_failure()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.status_code, enum.ORDER_ITEM_STATUS_CONTACT_FAILED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(len(item1.events), 2)
|
|
|
|
self.assertIsNone(item1.events[0].note)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].type_code, enum.ORDER_ITEM_EVENT_CONTACT_FAILED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(item1.events[1].note, "extra note")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
|
|
|
|
class TestDeliveryView(OrderItemViewTestMixin, WebTestCase):
|
|
|
|
|
|
|
|
def make_view(self):
|
|
|
|
return mod.DeliveryView(self.request)
|
|
|
|
|
|
|
|
def test_configure_grid(self):
|
|
|
|
model = self.app.model
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# nothing added without perms
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertFalse(grid.checkable)
|
|
|
|
self.assertEqual(len(grid.tools), 0)
|
|
|
|
|
|
|
|
# buttons added with perm
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(self.request, "is_root", new=True):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.configure_grid(grid)
|
|
|
|
self.assertEqual(len(grid.tools), 2)
|
|
|
|
self.assertTrue(grid.checkable)
|
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_delivery", grid.tools)
|
|
|
|
tool = grid.tools["process_delivery"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Delivered", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertIn("process_restock", grid.tools)
|
|
|
|
tool = grid.tools["process_restock"]
|
|
|
|
self.assertIn("<b-button ", tool)
|
|
|
|
self.assertIn("Restocked", tool)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_delivery(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_delivery", "/delivery/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_CONTACTED,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_delivery"
|
|
|
|
) as process_delivery:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_delivery)
|
|
|
|
process_delivery.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# all info provided
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": item1.uuid.hex,
|
|
|
|
"note": "extra note",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_delivery()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_DELIVERED)
|
|
|
|
self.assertEqual(len(item1.events), 2)
|
|
|
|
self.assertIsNone(item1.events[0].note)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].type_code, enum.ORDER_ITEM_EVENT_DELIVERED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(item1.events[1].note, "extra note")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
def test_process_restock(self):
|
2025-08-31 12:59:28 -05:00
|
|
|
self.pyramid_config.add_route("order_items_delivery", "/delivery/")
|
2025-01-22 21:32:15 -06:00
|
|
|
model = self.app.model
|
|
|
|
enum = self.app.enum
|
|
|
|
view = self.make_view()
|
|
|
|
grid = view.make_grid(model_class=model.OrderItem)
|
|
|
|
|
|
|
|
# sample data
|
2025-08-31 12:59:28 -05:00
|
|
|
user = model.User(username="barney")
|
2025-01-22 21:32:15 -06:00
|
|
|
self.session.add(user)
|
2025-08-31 12:59:28 -05:00
|
|
|
order = model.Order(
|
|
|
|
order_id=42, customer_name="Fred Flintstone", created_by=user
|
|
|
|
)
|
|
|
|
item1 = model.OrderItem(
|
|
|
|
order_qty=1,
|
|
|
|
order_uom=enum.ORDER_UOM_UNIT,
|
|
|
|
status_code=enum.ORDER_ITEM_STATUS_CONTACTED,
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
order.items.append(item1)
|
|
|
|
self.session.add(order)
|
|
|
|
self.session.flush()
|
|
|
|
|
|
|
|
# view only configured for POST
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.multiple(self.request, method="POST", user=user):
|
|
|
|
with patch.object(view, "Session", return_value=self.session):
|
2025-01-22 21:32:15 -06:00
|
|
|
|
|
|
|
# redirect if items not specified
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
view.order_handler, "process_restock"
|
|
|
|
) as process_restock:
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertRaises(HTTPFound, view.process_restock)
|
|
|
|
process_restock.assert_not_called()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertTrue(self.request.session.pop_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertFalse(self.request.session.peek_flash())
|
|
|
|
|
|
|
|
# all info provided
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_CONTACTED)
|
|
|
|
self.assertEqual(len(item1.events), 0)
|
2025-08-31 12:59:28 -05:00
|
|
|
with patch.object(
|
|
|
|
self.request,
|
|
|
|
"POST",
|
|
|
|
new={
|
|
|
|
"item_uuids": item1.uuid.hex,
|
|
|
|
"note": "extra note",
|
|
|
|
},
|
|
|
|
):
|
2025-01-22 21:32:15 -06:00
|
|
|
view.process_restock()
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertFalse(self.request.session.peek_flash("warning"))
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertTrue(self.request.session.pop_flash())
|
|
|
|
self.assertEqual(item1.status_code, enum.ORDER_ITEM_STATUS_RESTOCKED)
|
|
|
|
self.assertEqual(len(item1.events), 2)
|
|
|
|
self.assertIsNone(item1.events[0].note)
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[0].type_code, enum.ORDER_ITEM_EVENT_RESTOCKED
|
|
|
|
)
|
2025-01-22 21:32:15 -06:00
|
|
|
self.assertEqual(item1.events[1].note, "extra note")
|
2025-08-31 12:59:28 -05:00
|
|
|
self.assertEqual(
|
|
|
|
item1.events[1].type_code, enum.ORDER_ITEM_EVENT_NOTE_ADDED
|
|
|
|
)
|