Finance-Accounts/ar-aging-app/backend/tests/test_per_market.py

144 lines
6.0 KiB
Python

"""
Per-marketplace switcher: journal resolution, payout attribution, and the
`?marketplace=` API surface on Finance Summary / AR Ledger.
"""
from __future__ import annotations
import os
import tempfile
_TMP = tempfile.mkdtemp(prefix="ar_permarket_test_")
os.environ["AR_DATA_DIR"] = _TMP
from datetime import date # noqa: E402
from fastapi.testclient import TestClient # noqa: E402
from app.api.main import app # noqa: E402
from app.api.routes.ar import _journal_for, _market_list # noqa: E402
from app.db.database import init_db # noqa: E402
from app.core.settlements import aggregate, classify # noqa: E402
from tests.test_excel_export import make_amazon_xlsx # noqa: E402
from tests.test_multimarket import _make_dutch_file # noqa: E402
# ------------------------------------------------------------------ resolution helpers
PAYLOAD = {
"marketplace": "USA",
"marketplaces_included": ["USA", "Germany"],
"lines": [{"key": "Sales", "gl_account": "x", "values": [10.0], "total": 10.0}],
"per_marketplace": {
"USA": {"marketplace": "USA",
"lines": [{"key": "Sales", "gl_account": "x", "values": [10.0], "total": 10.0}]},
"Germany": {"marketplace": "Germany",
"lines": [{"key": "Sales", "gl_account": "y", "values": [7.0], "total": 7.0}]},
},
}
def test_market_list_puts_primary_first():
assert _market_list(PAYLOAD) == ["USA", "Germany"]
assert _market_list({"marketplace": "USA"}) == ["USA"]
def test_journal_resolution():
j, mkt = _journal_for(PAYLOAD, None)
assert mkt == "USA" and j["lines"][0]["total"] == 10.0
j, mkt = _journal_for(PAYLOAD, "Germany")
assert mkt == "Germany" and j["lines"][0]["total"] == 7.0
# Unknown marketplace falls back to the primary rather than erroring
j, mkt = _journal_for(PAYLOAD, "Narnia")
assert mkt == "USA"
# ------------------------------------------------------- payout owner attribution
def _rec(total, ttype, acct, sid, d, mkt):
return {"total": total, "txn_type": ttype, "_type_en": ttype, "account_type": acct,
"settlement_id": sid, "_date": d, "_marketplace": mkt,
"date_time": d.isoformat() if d else None}
def test_transfers_attribute_to_settlement_owner():
"""A payout sitting in the Germany file but tagged to a Belgium settlement is Belgium's.
(Real behaviour of the combined 'Belgium Germany' Amazon report.)
"""
recs = [
# Belgium owns settlement 500 (all its order rows are Belgian)
_rec(1000.0, "Order", "All Orders", "500", date(2026, 1, 10), "Belgium"),
_rec(500.0, "Order", "All Orders", "600", date(2026, 1, 25), "Belgium"),
# …but its payout row physically appears in the Germany file
_rec(-1000.0, "Transfer", "", "600", date(2026, 1, 20), "Germany"),
# Germany's own chain
_rec(4000.0, "Order", "All Orders", "700", date(2026, 1, 12), "Germany"),
]
agg = aggregate(recs)
cls = classify(agg, month_end=date(2026, 1, 31), clearing_lag_days=2)
assert cls.settlement_owner["500"] == "Belgium"
assert cls.settlement_owner["600"] == "Belgium"
owner = cls.settlement_owner
per: dict[str, float] = {}
for t in agg.transfers:
mkt = owner.get(t.settlement_id, t.marketplace)
per[mkt] = per.get(mkt, 0.0) + (t.amount if t.received else 0.0)
assert per.get("Belgium") == -1000.0 # attributed to the owner…
assert "Germany" not in per # …not the file it appeared in
# --------------------------------------------------------------- API surface
def test_per_market_endpoints():
init_db()
usa = os.path.join(_TMP, "USA synthetic.xlsx")
nl = os.path.join(_TMP, "Netherlands Amazon Transactions January, 2026.xlsx")
make_amazon_xlsx(usa, order_rows=8)
_make_dutch_file(nl)
with TestClient(app) as c:
sid = c.post("/api/sessions", json={
"name": "multi", "reporting_month": "2026-01",
"month_end_date": "2026-01-31", "clearing_lag_days": 2,
}).json()["id"]
for path in (usa, nl):
with open(path, "rb") as fh:
assert c.post(f"/api/sessions/{sid}/files",
files={"files": (os.path.basename(path), fh)}).status_code == 200
assert c.post(f"/api/sessions/{sid}/process").status_code == 200
assert c.get(f"/api/sessions/{sid}/status").json()["status"] == "processed"
mv = c.get(f"/api/sessions/{sid}/ar-movement").json()
assert mv["available"]
markets = mv["marketplaces"]
assert {"USA", "Netherlands"} <= set(markets)
assert markets[0] == mv["marketplace"] # primary first
nl_mv = c.get(f"/api/sessions/{sid}/ar-movement?marketplace=Netherlands").json()
assert nl_mv["marketplace"] == "Netherlands"
assert nl_mv["currency"] == "EUR"
# Netherlands receivable base is its own, not the session total
assert nl_mv["net_revenue"] != mv["net_revenue"]
# its single received payout (settlement 200 transfer, Jan 6)
assert nl_mv["received_payouts"] == -70.0
fs = c.get(f"/api/sessions/{sid}/finance-summary?marketplace=Netherlands").json()
assert fs["marketplace"] == "Netherlands"
assert fs["currency"] == "EUR"
assert set(fs["marketplaces"]) == set(markets)
assert fs["ledger"][0]["period"] == "Opening"
# Opening balance is per marketplace and flows into that market's roll-forward
c.put(f"/api/sessions/{sid}/opening-balances",
json=[{"marketplace": "Netherlands", "amount": 100.0,
"reason": "prior close", "source": "manual"}])
after = c.get(f"/api/sessions/{sid}/ar-movement?marketplace=Netherlands").json()
assert after["opening"] == 100.0
assert round(after["closing"], 2) == round(
100.0 + after["net_revenue"] + after["received_payouts"], 2)
# …and does not leak into the other marketplace
usa_mv = c.get(f"/api/sessions/{sid}/ar-movement?marketplace=USA").json()
assert usa_mv["opening"] == 0.0