dodil/erp-brick-sales-order
DODIL ERP brick: sales order
tests/scenarios/catalogue.py
"""so's scenario catalogue (module.yaml tests.scenarios), each mapped to its stories and written ONCE over
the composition's doors — routes, MCP tools, public links — never a table. The scenario steps need the product, tax,
payments and invoicing neighbours; they run in pytest with those as in-memory port fakes, and this catalogue keeps the
spec's ids and the generated permission probes (401/403/served for every role, every use case, both doors).
erp-scenarios list tests/scenarios/catalogue.py
erp-scenarios run tests/scenarios/catalogue.py
"""
import pathlib
from erp_platform.scenarios import Catalogue, Skip
HERE = pathlib.Path(__file__).resolve().parent
CAT = Catalogue("so", compose=HERE / "compose.yaml", roles=HERE / "roles.yaml")
# Generated over the composition's doors, both HTTP and MCP (erp_platform.scenarios.probes): every role ×
# every use case — 401 with no identity, 403 without the permission, served with it. The finer probes (a role
# the workflow does not name, state guards, a terminal parent, separation of duties, seams, idempotency,
# mass assignment) are tests/test_probes.py. Row visibility (another rep's order is a 404) is tests/test_access.py.
CAT.permission_matrix()
@CAT.scenario("SC-SO-01", "quote to order to invoice, ordered policy", stories=["SO-01", "SO-03", "SO-08"])
def sc_sc_so_01(s):
s.given("quote Q-2026-0042 v2 accepted, 3 service lines, policy ordered")
s.when("the acceptance is handed over twice, the order confirmed, the invoice requested twice")
s.then("one order, numbered at confirmation; one draft invoice with the 3 lines; the second request creates nothing")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-02", "part delivery, delivered policy", stories=["SO-07", "SO-08", "SO-12"])
def sc_sc_so_02(s):
s.given("a confirmed order of 12 units, policy delivered")
s.when("5 are delivered and invoiced, then 7 more delivered and invoiced")
s.then("two invoices of 5 and 7; the order is done after the second; recording DN-118 twice counts once")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-03", "deposit then final", stories=["SO-09", "SO-08"])
def sc_sc_so_03(s):
s.given("an order of 10,000.00 net with a 50% deposit")
s.when("the deposit is raised, then the full order invoiced")
s.then("a deposit invoice of 5,000.00 net and a final invoice of 10,000.00 net less the 5,000.00 deposit")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-04", "credit hold", stories=["SO-03", "SO-22"])
def sc_sc_so_04(s):
s.given("a customer with an invoice 75 days overdue and hold mode block")
s.when("the rep confirms")
s.then("refused with the reason; an order admin overrides with a reason and it confirms")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-05", "amend before delivery", stories=["SO-05"])
def sc_sc_so_05(s):
s.given("a confirmed order of 10, 4 delivered")
s.when("the rep changes 10 to 12, then to 3")
s.then("revision 2 at 12 after approval; 3 is refused (below 4 delivered)")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-06", "units and prices on a hand order", stories=["SO-02", "SO-05"])
def sc_sc_so_06(s):
s.given("COLA-075 at 1.20 a bottle (C62) and 24.00 a pack (PK24) on the customer's list from 2026-10-01, and 1.30 a bottle from 2026-12-01")
s.when("on 2026-11-02 the rep orders 2 x PK24 and 6 x C62, confirms, then on 2026-12-03 amends the bottles to 12")
s.then("the pack line is 48.00 with priced_from the pack's row; the bottle line was 7.20 and after the amendment is 15.60 with a new priced_from; the pack line is untouched; an order dated 2026-11-01 on 2026-11-02 is refused")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-07", "AI suggestion is only a suggestion", stories=["SO-14", "SO-16", "SO-03"])
def sc_sc_so_07(s):
s.given("the owner turned ai_order_from_po to suggest; PO-4471 arrives with a quantity that differs from the quote")
s.when("the suggestion is made, then the rep tries to confirm, then accepts the lines and marks the difference resolved")
s.then("the draft is empty until accepted; confirming with the difference open is refused (gate no_open_suggestion); so_suggestion records the rep's decisions")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")
@CAT.scenario("SC-SO-08", "Odoo migration reconciles to zero", stories=["SO-24"])
def sc_sc_so_08(s):
s.given("an Odoo test database with open, done and cancelled orders, a down payment, a line in a non-base unit and a section line")
s.when("the migration runs twice and the reconciliation is computed")
s.then("every check in migration.odoo.reconcile is equal or explained by a listed reason; the section line is counted as not carried; no invoice request exists for a history order")
raise Skip("written as a pytest scenario with the neighbours as port fakes: tests/test_scenarios_so.py (SC-SO-07: tests/test_stories.py; SC-SO-08: tests/test_migration.py)")