dodil/erp-brick-inventory
DODIL ERP brick: inventory
tests/test_returns.py
"""Customer returns against an RMA: INV-14 (receive at the cost they left at, inspect every unit, tell sales-order once)."""
from decimal import Decimal
import fakes
import world
from conftest import MANAGER, OPERATOR, SYSTEM, api, must, refused
from test_deliveries import BOTTLES, stocked
RMA = {"rma_ref": "so:RE-2026-0001", "bp_id": 7311, "warehouse_id": "WH1", "lines": [
{"line_no": 1, "product_id": "COLA-075", "unit_code": "C62", "quantity": "6", "original_delivery_ref": "WH1/OUT/00001", "inspect": True}]}
def delivered_and_authorised():
stocked(pallets="2", order_qty="24")
must(api("POST", "/locations", MANAGER, {"location_id": "WH1/QC", "kind": "internal", "stock_status": "quality"}))
doc = must(api("POST", "/deliveries", OPERATOR, {"sales_order_id": "SO-2026-0012", "warehouse_id": "WH1"}))["document_id"]
must(api("POST", f"/deliveries/{doc}/validate", OPERATOR, {"picks": [{"line_no": 1, "location_id": "WH1/A/01", "quantity": "24", "unit_code": "C62"}]}))
fakes.add_order("RE-2026-0001", {1: {**BOTTLES, "quantity": "6"}})
return must(api("POST", "/expected-returns", SYSTEM, RMA))
def receive(qty="6", **kw):
return api("POST", "/returns", OPERATOR, {"bp_id": 7311, "warehouse_id": "WH1", "rma_ref": "so:RE-2026-0001",
"lines": [{"line_no": 1, "quantity": qty, "unit_code": "C62"}], **kw})
def test_inv14_an_rma_is_idempotent_cancellable_until_something_is_received():
delivered_and_authorised()
again = must(api("POST", "/expected-returns", SYSTEM, RMA))
assert again["replay"] is True and again["expected"] == [{"line_no": 1, "quantity_base": "4.5"}]
assert api("POST", "/expected-returns", MANAGER, RMA)[0] == 403 # a machine grant
assert must(api("POST", "/expected-returns/so:RE-2026-0001/cancel", SYSTEM, {}))["status"] == "cancelled"
s, b = receive()
assert s == 409 and b["detail"]["gate"] == "rma_open" and "no open RMA of 7311" in b["detail"]["reason"] # a cancelled RMA lets nothing be received
assert must(api("POST", "/expected-returns/so:RE-2026-0001/cancel", SYSTEM, {}))["status"] == "cancelled" # a repeat answers the same
refused(api("POST", "/expected-returns/so:NOPE/cancel", SYSTEM, {}), 422, "no RMA so:NOPE")
fakes.add_order("RE-2026-0002", {1: {**BOTTLES, "quantity": "6"}})
must(api("POST", "/expected-returns", SYSTEM, {**RMA, "rma_ref": "so:RE-2026-0002"}))
must(receive(rma_ref="so:RE-2026-0002"))
s, b = api("POST", "/expected-returns/so:RE-2026-0002/cancel", SYSTEM, {})
assert s == 422 and b["detail"]["gate"] == "rma_received" and b["detail"]["reason"] == "so:RE-2026-0002 is partly received: it can no longer be cancelled"
def test_inv14_receive_into_quality_at_the_cost_it_left_at_then_inspect_every_unit_and_tell_the_order_once():
delivered_and_authorised()
# today's average differs from the delivery's cost: a second receipt at a higher price
world.receive("COLA-075", "1", "PK24", price="36.00", location="WH1/A/02")
got = must(receive())
assert got["number"] == "WH1/RET/00001" and got["delivery_ref"] == "inv:WH1/RET/00001"
(mv,) = got["moves"]
assert (mv["from_location_id"], mv["to_location_id"], mv["quantity_base"]) == ("UK1:CUSTOMER", "WH1/QC", "4.5")
assert mv["value_amount"] == "5.40" # the cost it left at (24 bottles = 21.60), not today's average
assert fakes.ORDERS["RE-2026-0001"]["lines"][1]["delivered"] == Decimal(6)
assert must(api("POST", "/returns", OPERATOR, {"bp_id": 7311, "warehouse_id": "WH1", "request_id": got["document_id"].split(":")[-1], "lines": []}))["replay"] is True
doc = got["document_id"]
refused(api("POST", f"/returns/{doc}/inspect", OPERATOR, {"decisions": [{"line_no": 1, "decision": "restock", "quantity": "4", "unit_code": "C62"}]}),
409, "every_unit_decided")
done = must(api("POST", f"/returns/{doc}/inspect", OPERATOR, {"decisions": [
{"line_no": 1, "decision": "restock", "quantity": "4", "unit_code": "C62"},
{"line_no": 1, "decision": "scrap", "quantity": "1", "unit_code": "C62", "reason": "damaged by us"},
{"line_no": 1, "decision": "back_to_customer", "quantity": "1", "unit_code": "C62", "reason": "damaged by the customer"}]}))
by = {m["decision"]: m for m in done["moves"]}
assert (by["restock"]["to_location_id"], by["restock"]["quantity_base"], by["restock"]["value_amount"]) == ("WH1/A/01", "3", "0.00")
assert (by["scrap"]["to_location_id"], by["scrap"]["quantity_base"]) == ("UK1:LOSS", "0.75")
assert by["back_to_customer"]["to_location_id"] == "UK1:CUSTOMER" and by["back_to_customer"]["value_amount"] == "-0.90"
assert by["scrap"]["value_amount"] == "-0.90" # the value it came in
(insp,) = fakes.RETURN_INSPECTIONS.values()
assert (insp["received_quantity"], insp["accepted_quantity"], insp["rejected_quantity"]) == ("6", "5", "1")
assert insp["inspection_ref"] == "inv:WH1/RET/00001:1"
again = must(api("POST", f"/returns/{doc}/inspect", OPERATOR, {"decisions": []}))
assert again["replay"] is True and len(fakes.RETURN_INSPECTIONS) == 1 # once
assert [i["reported_to_order"] for i in must(api("GET", f"/returns/{doc}", OPERATOR))["inspections"]] == [True, True, True]
assert {j["event"] for j in fakes.JOURNALS.values()} >= {"return", "return_inspection"}
def test_inv14_a_parcel_with_no_rma_more_than_expected_or_a_serial_never_delivered_is_refused():
delivered_and_authorised()
body = {"bp_id": 7311, "warehouse_id": "WH1", "lines": [{"line_no": 1, "product_id": "SCREW-M4", "quantity": "1", "unit_code": "C62"}]}
refused(api("POST", "/returns", OPERATOR, body), 409, "no open RMA of 7311 names SCREW-M4")
refused(api("POST", "/returns", OPERATOR, {**body, "bp_id": 9999, "lines": [{**body["lines"][0], "product_id": "COLA-075"}]}), 409, "no open RMA of 9999 names COLA-075")
refused(receive("7"), 409, "expects 4.5 LTR more, 5.25 came")
s, got = api("POST", "/returns", OPERATOR, {"bp_id": 7311, "warehouse_id": "WH1", "rma_ref": "so:RE-2026-0001",
"lines": [{"line_no": 1, "quantity": "2", "unit_code": "C62", "serials": ["S-1", "S-2"]}]})
assert s == 409 and got["detail"]["gate"] == "serial_was_delivered" and "never delivered to customer 7311" in got["detail"]["reason"]
assert must(api("GET", "/stock/COLA-075", OPERATOR))["on_hand"] == "18" # nothing was written