dodil/erp-brick-purchasing
DODIL ERP brick: purchasing
tests/test_stories_evaluation.py
"""PUR-46: the supplier scorecard."""
from conftest import ALICE, BUYER, CLERK, OWNER, SUPPLIER_A, api, confirmed, must, receive, supplier_price, world
def trade(n_late=1, n=5):
world()
supplier_price()
o = confirmed(lines=[{"product_id": "COLA-075", "quantity": "1", "expected_on": "2026-11-10"}])
for i in range(n):
receive(o, quantity="1", note=f"DN-{i}", on="2026-11-19" if i < n_late else "2026-11-10")
return o
def test_pur46_scores_are_derived_from_rows_and_the_overall_is_the_weighted_sum():
trade()
got = must(api("GET", f"/suppliers/{SUPPLIER_A}/evaluation?period_id=2026-Q4", BUYER))
assert got["scores"]["delivery"] == 80 and got["scores"]["quality"] == 100 and got["scores"]["service"] is None
must(api("POST", f"/suppliers/{SUPPLIER_A}/service-score", BUYER, {"period_id": "2026-Q4", "score": 4}))
got = must(api("GET", f"/suppliers/{SUPPLIER_A}/evaluation?period_id=2026-Q4", BUYER))
w = got["weights"]
want = sum(w[c] * got["scores"][c] for c in w) / sum(w.values())
assert got["scores"]["service"] == 80 and abs(float(got["overall"]) - want) < 0.01
def test_pur46_freezing_writes_once_and_a_reevaluation_is_a_new_row_with_a_reason():
trade()
f = must(api("POST", f"/suppliers/{SUPPLIER_A}/evaluations", BUYER, {"period_id": "2026-Q4"}))
assert f["seq"] == 1 and f["frozen"] is True
s, b = api("POST", f"/suppliers/{SUPPLIER_A}/evaluations", BUYER, {"period_id": "2026-Q4"})
assert s == 409 and "reason" in str(b)
assert must(api("POST", f"/suppliers/{SUPPLIER_A}/evaluations", BUYER, {"period_id": "2026-Q4", "reason": "late receipt booked"}))["seq"] == 2
assert api("POST", f"/suppliers/{SUPPLIER_A}/service-score", BUYER, {"period_id": "2026-Q4", "score": 5})[0] == 409
items = must(api("GET", f"/suppliers/{SUPPLIER_A}/evaluations", BUYER))["items"]
assert [i["seq"] for i in items] == [1, 2]
def test_pur46_weights_are_owner_controlled_dated_rows_and_access_is_scoped():
trade()
assert api("POST", "/evaluation-weights", BUYER, {"valid_from": "2026-12-01", "price": 0, "delivery": 100, "quantity": 0, "quality": 0, "service": 0})[0] == 403
assert api("POST", "/evaluation-weights", OWNER, {"valid_from": "2026-11-20", "price": 0, "delivery": 100, "quantity": 0, "quality": 0, "service": 0})[0] == 409
must(api("POST", "/evaluation-weights", OWNER, {"valid_from": "2026-12-01", "price": 0, "delivery": 100, "quantity": 0, "quality": 0, "service": 0}))
assert must(api("GET", f"/suppliers/{SUPPLIER_A}/evaluation?period_id=2026-Q4", BUYER))["weights"]["delivery"] == 100 # in force at the period's end
assert must(api("GET", f"/suppliers/{SUPPLIER_A}/evaluation?period_id=2026-Q3", BUYER))["weights"]["delivery"] == 25
assert api("GET", f"/suppliers/{SUPPLIER_A}/evaluation?period_id=2026-Q4", CLERK)[0] == 403
assert api("GET", f"/suppliers/{SUPPLIER_A}/evaluation?period_id=nonsense", ALICE)[0] == 422