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"