dodil/erp-brick-purchasing
DODIL ERP brick: purchasing
tests/test_v8.py
"""Spec v8 (PUR-67 cancelled requests, PUR-28 the bill's particulars to tax, PUR-17 cost_centre_open, the address object)."""
from conftest import ALICE, APPROVER, BUYER, CLERK, PAYAPPROVER, REQUESTER, REQUESTER2, SUPPLIER_A, api, as_role, bill, must, refused, supplier_price, world
from conftest import confirmed as confirmed_order
SO_CLIENT = as_role("sales_order_client", "sales-order@shop.example")
ADDRESS = {"lines": ["1 High Street"], "city": "London", "postcode": "N1 1AA", "country": "GB"}
LINE = {"product_id": "COLA-075", "quantity": "40", "unit_code": "PK24", "sales_order_id": "SO-8", "so_line_no": 1}
def request_of(who=REQUESTER, price="18.0000", submit=False):
r = must(api("POST", "/purchase-requests", who, {"cost_centre": "OPS", "reason": "stock", "lines": [{"product_id": "COLA-075", "quantity": "10",
"unit_code": "PK24", "unit_price": price}]}))
if submit:
must(api("POST", f"/purchase-requests/{r['purchase_request_id']}/submit", who, {}))
return r["purchase_request_id"]
def test_pur67_a_requester_cancels_their_own_request_a_buyer_any_and_it_records_who_when_why():
world()
mine, other = request_of(submit=True), request_of(REQUESTER2)
assert api("POST", f"/purchase-requests/{other}/cancel-request", REQUESTER, {"reason": "x"})[0] == 404 # not theirs
out = must(api("POST", f"/purchase-requests/{mine}/cancel-request", REQUESTER, {"reason": "no longer needed"}))
assert out["status"] == "cancelled" and out["cancelled_by_email"] == "requester@shop.example" and out["cancel_reason"] == "no longer needed"
assert out["cancelled_on"] == "2026-11-20"
assert must(api("POST", f"/purchase-requests/{mine}/cancel-request", REQUESTER, {"reason": "again"}))["status"] == "cancelled"
assert mine not in [x["purchase_request_id"] for x in must(api("GET", "/purchase-requests?status=awaiting_approval", ALICE))["requests"]]
assert must(api("POST", f"/purchase-requests/{other}/cancel-request", BUYER, {"reason": "duplicate"}))["status"] == "cancelled"
assert api("POST", f"/purchase-requests/{mine}/approve", APPROVER, {})[0] == 409
def test_pur67_an_ordered_request_is_cancelled_only_through_its_order_which_cancels_it_with_the_order_reason():
world()
rid = request_of(submit=True)
must(api("POST", f"/purchase-requests/{rid}/approve", APPROVER, {}))
supplier_price()
o = must(api("POST", f"/purchase-requests/{rid}/raise-order", BUYER, {"bp_id": SUPPLIER_A}))
s, b = api("POST", f"/purchase-requests/{rid}/cancel-request", BUYER, {"reason": "x"})
assert s == 422 and "not_ordered" in b["detail"] # whether or not the supplier confirmed
must(api("POST", f"/purchase-orders/{o['purchase_order_id']}/confirm", BUYER, {}))
assert api("POST", f"/purchase-requests/{rid}/cancel-request", BUYER, {"reason": "x"})[0] == 422
must(api("POST", f"/purchase-orders/{o['purchase_order_id']}/cancel", BUYER, {"reason": "supplier cannot deliver"}))
got = must(api("GET", f"/purchase-requests/{rid}", ALICE))
assert got["status"] == "cancelled" and got["cancel_reason"] == "supplier cannot deliver" and got["cancelled_by_email"] == "buyer@shop.example"
def test_pur67_sales_order_calls_again_after_the_order_is_cancelled_and_gets_200():
world()
rid = must(api("POST", "/purchase-requests/from-sales-order", SO_CLIENT, {"request_ref": "so:SO-8:1@r1", **LINE}))["request_id"]
must(api("POST", f"/purchase-requests/{rid}/approve", APPROVER, {}))
supplier_price()
o = must(api("POST", f"/purchase-requests/{rid}/raise-order", BUYER, {"bp_id": SUPPLIER_A}))
s, b = api("POST", f"/purchase-requests/{rid}/cancel", SO_CLIENT, {"reason": "customer cancelled"})
assert s == 422 and b["detail"]["gate"] == "not_cancellable" if isinstance(b["detail"], dict) else "ordered" in b["detail"]
must(api("POST", f"/purchase-orders/{o['purchase_order_id']}/cancel", BUYER, {"reason": "customer cancelled"}))
assert must(api("POST", f"/purchase-requests/{rid}/cancel", SO_CLIENT, {"reason": "again"})) == {"request_id": rid, "status": "cancelled"}
def test_the_ship_to_is_the_address_object_and_a_legacy_string_is_one_line(neighbours):
world()
a = must(api("POST", "/purchase-requests/from-sales-order", SO_CLIENT, {"request_ref": "so:SO-8:2@r1", **LINE, "so_line_no": 2, "ship_to_bp_id": 7001,
"ship_to_address": ADDRESS}))
got = must(api("GET", f"/purchase-requests/{a['request_id']}", ALICE))
assert got["ship_to_address_city"] == "London" and got["ship_to_address_country"] == "GB" and got["ship_to_address_lines"] == '["1 High Street"]'
b = must(api("POST", "/purchase-requests/from-sales-order", SO_CLIENT, {"request_ref": "so:SO-8:3@r1", **LINE, "so_line_no": 3, "ship_to_bp_id": 7001,
"ship_to_address": "1 High Street, London"}))
old = must(api("GET", f"/purchase-requests/{b['request_id']}", ALICE))
assert old["ship_to_address_lines"] == '["1 High Street, London"]' and old["ship_to_address_country"] == "GB" and old["ship_to_address_city"] == ""
assert api("POST", "/purchase-requests/from-sales-order", SO_CLIENT, {"request_ref": "so:SO-8:4@r1", **LINE, "so_line_no": 4,
"ship_to_address": {**ADDRESS, "country": "gb"}})[0] == 422
def test_pur28_the_content_check_and_the_freeze_carry_the_bills_particulars(neighbours):
world()
supplier_price()
o = confirmed_order(lines=[{"product_id": "COLA-075", "quantity": "10"}])
must(api("POST", f"/purchase-orders/{o['purchase_order_id']}/receipts", as_role("receiver", "receiver@shop.example"),
{"delivery_note": "DN-1", "received_on": "2026-11-19", "location_id": "WH/STOCK", "lines": [{"line_no": 1, "quantity": "10"}]}))
b = must(bill(o, supplier_address=ADDRESS, supplier_vat_number="GB999888777"))
must(api("POST", f"/bills/{b['bill_id']}/approve", PAYAPPROVER, {}))
sent = neighbours.tax.contents
assert sent["invoice_number"] == "INV-5521" and sent["issue_date"] == "2026-11-19" and sent["supplier_address"]["city"] == "London"
assert sent["supplier_vat_number"] == "GB999888777" and sent["customer_name"] == "Brightside Ltd" and sent["supplier_name"].startswith("")
assert neighbours.tax.frozen_contents == sent
def test_pur28_a_vat_number_the_clerk_did_not_give_defaults_to_taxs_and_none_is_sent_absent(neighbours):
world()
supplier_price()
o = confirmed_order(lines=[{"product_id": "COLA-075", "quantity": "10"}])
b = must(bill(o))
assert b["supplier_vat_number"] == "GB123" # tax's party record (the fake's)
neighbours.tax.party = lambda bp_id: {"bp_id": bp_id, "current": None, "rows": []}
o2 = confirmed_order(lines=[{"product_id": "COLA-075", "quantity": "5"}])
b2 = must(bill(o2, number="INV-2", quantity="5"))
assert b2["supplier_vat_number"] == ""
must(api("POST", f"/purchase-orders/{o2['purchase_order_id']}/receipts", as_role("receiver", "receiver@shop.example"),
{"delivery_note": "DN-2", "received_on": "2026-11-19", "location_id": "WH/STOCK", "lines": [{"line_no": 1, "quantity": "5"}]}))
api("POST", f"/bills/{b2['bill_id']}/approve", PAYAPPROVER, {})
assert "supplier_vat_number" not in neighbours.tax.contents and "supplier_address" not in neighbours.tax.contents
def _bill_on(cost_centre):
supplier_price()
o = confirmed_order(lines=[{"product_id": "COLA-075", "quantity": "10", "cost_centre": cost_centre}])
must(api("POST", f"/purchase-orders/{o['purchase_order_id']}/receipts", as_role("receiver", "receiver@shop.example"),
{"delivery_note": "DN-9", "received_on": "2026-11-19", "location_id": "WH/STOCK", "lines": [{"line_no": 1, "quantity": "10"}]}))
return must(bill(o))
def test_pur17_approve_bill_checks_every_cost_centre_against_gl(neighbours):
world()
b = _bill_on("OPS")
neighbours.gl.extra_cost_centres["OPS"] = "closed"
assert "cost_centre_open" in refused(api("POST", f"/bills/{b['bill_id']}/approve", PAYAPPROVER, {}))
neighbours.gl.extra_cost_centres["OPS"] = "unknown"
assert "unknown to the ledger" in refused(api("POST", f"/bills/{b['bill_id']}/approve", PAYAPPROVER, {}))
neighbours.gl.extra_cost_centres.clear()
assert must(api("POST", f"/bills/{b['bill_id']}/approve", PAYAPPROVER, {}))["status"] == "approved"