""" Accounts Summary — the cross-month view of APPROVED journal entries. One row per (month, marketplace): the journal's GL lines (accrual presentation — Transfer excluded, Receivable = net revenue Dr A/R) in local currency plus the session's FX rate, so the UI can show any marketplace across every month, or all marketplaces converted to USD. Only months whose journal has been APPROVED appear — approval on the Journal Entry tab is the publish step. A month drops out again if its sign-off is withdrawn, if it is re-processed (the sign-off clears with the numbers), or if a month-end control blocks it. """ from __future__ import annotations import json from fastapi import APIRouter, Depends from sqlalchemy.orm import Session as OrmSession from ...core.journal import ACCRUAL_DISPLAY_EXCLUDES, LINE_ACCOUNTS, RECEIVABLE_KEY from ...db import models from ..deps import db_dep, is_blocked router = APIRouter(prefix="/api", tags=["accounts-summary"]) @router.get("/accounts-summary") def accounts_summary(db: OrmSession = Depends(db_dep)) -> dict: from .ar import _journal_for, _market_list, fx_for entries = ( db.query(models.JournalEntry, models.Session) .join(models.Session, models.Session.id == models.JournalEntry.session_id) .filter(models.JournalEntry.approved_by != "") .order_by(models.Session.reporting_month, models.Session.id) .all() ) line_keys = [k for k, _ in LINE_ACCOUNTS if k not in ACCRUAL_DISPLAY_EXCLUDES] months: list[dict] = [] cells: list[dict] = [] marketplaces: set[str] = set() for j, s in entries: if is_blocked(s) or not j.data: continue # a blocked closing publishes nothing payload = json.loads(j.data) month = s.reporting_month or (s.month_end_date.isoformat()[:7] if s.month_end_date else f"session-{s.id}") months.append({ "month": month, "session_id": s.id, "session_name": s.name, "reviewed_by": j.reviewed_by or "", "approved_by": j.approved_by or "", "approved_at": j.approved_at.isoformat() if j.approved_at else None, "entry_no": j.entry_no or "", }) for mkt in _market_list(payload): sub, _ = _journal_for(payload, mkt) rate, currency = fx_for(db, s.id, mkt) values = {ln["key"]: ln["total"] for ln in sub.get("lines", []) if ln["key"] not in ACCRUAL_DISPLAY_EXCLUDES} accrual = sub.get("receivable_accrual") if accrual is None: # Payload stored before the accrual figure existed: derive it. accrual_total = -round(sum(values.values()), 2) else: accrual_total = accrual["total"] marketplaces.add(mkt) cells.append({ "month": month, "session_id": s.id, "marketplace": mkt, "currency": currency, "fx_rate": rate, "values": values, # line key -> local-currency total "receivable": accrual_total, # Dr A/R (net revenue accrued) }) return { "available": bool(months), "line_keys": line_keys, "receivable_key": RECEIVABLE_KEY, "months": months, "marketplaces": sorted(marketplaces), "cells": cells, }