dodil/erp-brick-payments

DODIL ERP brick: payments

tests/test_seams.py

"""The neighbours - invoicing (ar), the partner register (bp), the ledger (gl) - each behind one adapter: every answer is a
reply, never an exception. Not installed (409), failed (502), silent (503); the ledger is optional."""
from conftest import CASHIER, SALES, api, bank, must, receipt, refused, settle
from erp_platform import seam


def test_without_the_partner_register_a_receipt_cannot_be_recorded(nb):
    seam.unregister_local("business-partner")
    refused(api("POST", "/receipts", CASHIER, {"bp_id": 7311, "amount": "1.00", "currency": "GBP", "received_on": "2026-11-20"}), 409,
            "partner register (business-partner) is not part of this installation")


def test_a_register_that_fails_is_reported_as_what_it_did(nb):
    from fastapi import FastAPI, Response
    app = FastAPI()

    @app.get("/partners/{bp_id}")
    def partner(bp_id: int, response: Response):
        response.status_code = 500
        return {"detail": "boom"}
    seam.register_local("business-partner", app)
    refused(api("POST", "/receipts", CASHIER, {"bp_id": 7311, "amount": "1.00", "currency": "GBP", "received_on": "2026-11-20"}), 502,
            "the partner register failed (500")


def test_without_invoicing_cash_is_recorded_but_nothing_can_be_settled(nb):
    seam.unregister_local("ar")
    r = receipt(amount="10.00", reference="x")                                  # records cash only
    assert r["unallocated"] == "10.00" and r["suggestion_result"]["suggested"] is False
    refused(api("POST", f"/receipts/{r['payment_id']}/allocate", CASHIER, {"lines": [{"invoice_id": "I", "amount": "1.00"}]}), 409,
            "invoicing (ar) is not part of this installation")
    assert api("GET", "/customers/7311/open-items", CASHIER)[0] == 409
    assert must(api("GET", f"/receipts/{r['payment_id']}", CASHIER))["readiness"][2]["met"] is False        # within_remainder says why


def test_invoicing_down_is_a_502_never_a_500_and_credit_is_never_guessed(nb):
    nb.invoice("A", 7311, "10.00", due_date="2026-09-01")
    nb.ar_status = 500
    refused(api("GET", "/settlement", CASHIER, {"invoice_ids": "A"}), 502, "invoicing failed (500")
    refused(api("GET", "/customers/7311/credit-status", SALES), 502, "invoicing failed")
    r = receipt(amount="10.00", reference="y")                                   # a receipt can still be recorded
    assert r["suggestion_result"]["suggested"] is False and "invoicing failed" in r["suggestion_result"]["reason"]
    nb.ar_status = None
    assert settle("A")["A"]["outstanding"] == "10.00"


def test_the_ledger_is_optional_and_a_missing_ledger_account_is_said(nb):
    seam.unregister_local("gl")
    bank()
    r = receipt(amount="10.00", reference="x", bank_account_id="BANK1")
    assert r["ledger"]["posted"] is False and "no ledger is installed" in r["ledger"]["reason"]
    seam.register_local("gl", nb.gl)
    bank("BANK2", gl="")
    r2 = receipt(amount="10.00", reference="y", bank_account_id="BANK2")
    assert "has no ledger account" in r2["ledger"]["reason"]
    r3 = receipt(amount="10.00", reference="z")
    assert "(none)" in r3["ledger"]["reason"]


def test_a_receipt_posts_once_under_an_id_derived_from_its_payment_id(nb):
    bank()
    r = receipt(amount="1200.00", reference="x", bank_account_id="BANK1")
    j = nb.journals[f"receipt:{r['payment_id']}"]
    assert [(ln.get("account_id") or ln["purpose"], ln.get("debit"), ln.get("credit")) for ln in j["lines"]] == \
        [("1200", "1200.00", None), ("receivable", None, "1200.00")]
    assert (j["source_document"], j["event"], j["source_module"]) == (r["payment_id"], "receipt", "payments") and j["journal_id"].isdigit()
    assert len(nb.journals) == 1 and j["journal_date"] == "2026-11-20" and j["currency"] == "GBP"


def test_a_settlement_names_unknown_ids_and_skips_draft_and_void_invoices(nb):
    nb.invoice("OK", 7311, "10.00")
    nb.invoice("DRAFT", 7311, "10.00", state="draft")
    nb.invoice("VOID", 7311, "10.00", state="void")
    got = must(api("GET", "/settlement", CASHIER, {"invoice_ids": "OK,DRAFT,VOID,NOPE"}))
    assert [i["invoice_id"] for i in got["invoices"]] == ["OK"] and got["unknown"] == ["DRAFT", "VOID", "NOPE"]