144 lines
6.0 KiB
Python
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
|