from fastapi import APIRouter, HTTPException, Query from typing import Optional, List, Dict, Any from decimal import Decimal from datetime import date from app.core.database import get_connection router = APIRouter(prefix="/ledger", tags=["元帳"]) def to_decimal(x) -> Decimal: if isinstance(x, Decimal): return x return Decimal(str(x or 0)) @router.get("/{account_id}", summary="科目別元帳(残高推移)") def get_ledger( account_id: int, date_from: Optional[str] = Query(None, description="YYYY-MM-DD"), date_to: Optional[str] = Query(None, description="YYYY-MM-DD"), ): # 1) 科目存在チェック with get_connection() as conn, conn.cursor() as cur: cur.execute(""" SELECT account_id, account_code, account_name, account_type FROM accounts WHERE account_id = %s """, (account_id,)) account = cur.fetchone() if not account: raise HTTPException(status_code=404, detail="指定した科目が見つかりません。") # 2) 期間デフォルト(最小〜最大) if not date_from: cur.execute(""" SELECT MIN(j.journal_date) AS min_d FROM journal_entries j JOIN journal_lines l ON l.journal_id = j.journal_id WHERE l.account_id = %s """, (account_id,)) row = cur.fetchone() date_from = (row["min_d"] or date.today()).isoformat() if not date_to: cur.execute(""" SELECT MAX(j.journal_date) AS max_d FROM journal_entries j JOIN journal_lines l ON l.journal_id = j.journal_id WHERE l.account_id = %s """, (account_id,)) row = cur.fetchone() date_to = (row["max_d"] or date.today()).isoformat() # 3) 期首残高(date_from より前) cur.execute(""" SELECT COALESCE(SUM(l.debit - l.credit), 0) AS opening FROM journal_lines l JOIN journal_entries j ON j.journal_id = l.journal_id WHERE l.account_id = %s AND j.journal_date < %s """, (account_id, date_from)) opening = to_decimal(cur.fetchone()["opening"]) # 4) 期間内明細 cur.execute(""" SELECT j.journal_date, j.journal_id, j.description, l.debit, l.credit FROM journal_lines l JOIN journal_entries j ON j.journal_id = l.journal_id WHERE l.account_id = %s AND j.journal_date BETWEEN %s AND %s ORDER BY j.journal_date, j.journal_id, l.line_id """, (account_id, date_from, date_to)) rows = cur.fetchall() # 5) 残高推移の計算(借方−貸方) balance = opening out_rows: List[Dict[str, Any]] = [] total_debit = Decimal("0") total_credit = Decimal("0") for r in rows: d = to_decimal(r["debit"]) c = to_decimal(r["credit"]) total_debit += d total_credit += c balance += (d - c) out_rows.append({ "date": r["journal_date"].isoformat(), "journal_id": r["journal_id"], "description": r["description"], "debit": str(d), "credit": str(c), "balance": str(balance) # 借方残を正とする(借方−貸方) }) result = { "account": { "id": account["account_id"], "code": account["account_code"], "name": account["account_name"], "type": account["account_type"], }, "period": {"from": date_from, "to": date_to}, "opening_balance": str(opening), "total_debit": str(total_debit), "total_credit": str(total_credit), "closing_balance": str(balance), "rows": out_rows } return result