from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel from typing import List from decimal import Decimal from app.core.database import get_connection router = APIRouter(prefix="/opening-balances", tags=["期首残高"]) # ---------- Models ---------- class OpeningBalanceItem(BaseModel): account_id: int debit: Decimal = Decimal("0") credit: Decimal = Decimal("0") class OpeningBalanceRequest(BaseModel): fiscal_year: int balances: List[OpeningBalanceItem] # ---------- GET:期首残高取得 ---------- @router.get("", summary="期首残高取得") def get_opening_balances( fiscal_year: int = Query(..., description="会計年度(例:2025)") ): with get_connection() as conn, conn.cursor() as cur: cur.execute(""" SELECT a.account_id, a.account_code, a.account_name, a.account_type, COALESCE(o.opening_debit, 0) AS opening_debit, COALESCE(o.opening_credit, 0) AS opening_credit FROM accounts a LEFT JOIN opening_balances o ON o.account_id = a.account_id AND o.fiscal_year = %s WHERE a.is_active = true ORDER BY a.account_code """, (fiscal_year,)) return cur.fetchall() # ---------- POST:期首残高保存 ---------- @router.post("", summary="期首残高保存") def save_opening_balances(req: OpeningBalanceRequest): # 年度锁定チェック cur.execute(""" SELECT is_locked FROM fiscal_year_locks WHERE fiscal_year = %s """, (req.fiscal_year,)) row = cur.fetchone() if row and row["is_locked"]: raise HTTPException( status_code=400, detail="この会計年度の期首残高は既に確定されています。" ) total_debit = Decimal("0") total_credit = Decimal("0") for b in req.balances: total_debit += b.debit total_credit += b.credit if total_debit != total_credit: raise HTTPException( status_code=400, detail="借方合計と貸方合計が一致していません。" ) with get_connection() as conn, conn.cursor() as cur: # 既存データ削除(年度単位) cur.execute(""" DELETE FROM opening_balances WHERE fiscal_year = %s """, (req.fiscal_year,)) # 再登録 for b in req.balances: if b.debit == 0 and b.credit == 0: continue cur.execute(""" INSERT INTO opening_balances (fiscal_year, account_id, opening_debit, opening_credit) VALUES (%s, %s, %s, %s) """, ( req.fiscal_year, b.account_id, b.debit, b.credit )) conn.commit() return {"message": "期首残高を保存しました。"}