from fastapi import APIRouter, Query from datetime import date from typing import Optional from app.core.database import get_connection router = APIRouter(prefix="/trial-balance", tags=["试算表"]) @router.get("", summary="试算表(全科目)") def get_trial_balance( fiscal_year: Optional[int] = Query(None, description="会计年度(可选)"), date_from: Optional[date] = Query(None, description="开始日期(可选)"), date_to: Optional[date] = Query(None, description="结束日期(可选)"), ): print("### trial-balance OPTIONAL PARAMS ACTIVE ###") with get_connection() as conn: # 检查 is_latest 字段是否存在(新的版本追踪系统) with conn.cursor() as check_cur: check_cur.execute(""" SELECT COUNT(*) as cnt FROM information_schema.columns WHERE table_name = 'journal_entries' AND column_name = 'is_latest' """) has_is_latest = check_cur.fetchone()[0] > 0 # 构建查询语句 if has_is_latest: # 新系统:只取最新版本(is_latest = true) sql = """ SELECT a.account_id, a.account_code, a.account_name, a.account_type, COALESCE(SUM(l.debit), 0) AS debit, COALESCE(SUM(l.credit), 0) AS credit FROM journal_lines l JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id JOIN accounts a ON a.account_id = l.account_id WHERE e.is_deleted = false AND e.is_latest = true AND a.account_code NOT IN ('512', '513', '516') """ else: # 旧系统:排除已修正的记录(使用 parent_entry_id) sql = """ SELECT a.account_id, a.account_code, a.account_name, a.account_type, COALESCE(SUM(l.debit), 0) AS debit, COALESCE(SUM(l.credit), 0) AS credit FROM journal_lines l JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id JOIN accounts a ON a.account_id = l.account_id WHERE e.is_deleted = false AND a.account_code NOT IN ('512', '513', '516') AND e.journal_entry_id NOT IN ( SELECT parent_entry_id FROM journal_entries WHERE parent_entry_id IS NOT NULL ) """ params = {} if fiscal_year is not None: sql += " AND e.fiscal_year = %(fiscal_year)s" params["fiscal_year"] = fiscal_year if date_from is not None: sql += " AND e.entry_date >= %(date_from)s" params["date_from"] = date_from if date_to is not None: sql += " AND e.entry_date <= %(date_to)s" params["date_to"] = date_to sql += """ GROUP BY a.account_id, a.account_code, a.account_name, a.account_type ORDER BY CASE WHEN a.account_code < '509' THEN a.account_code WHEN a.account_code IN ('510', '511') THEN '508.' || a.account_code ELSE a.account_code END """ with get_connection() as conn, conn.cursor() as cur: cur.execute(sql, params) rows = cur.fetchall() return rows @router.get("/fiscal-years", summary="仕訳データから会計年度一覧取得(5月期)") def get_fiscal_years(): """ 仕訳の最小・最大日付から5月期の年度リストを生成して返す。 例: 2025-06-01〜2026-05-31 → {"label": "令和8年(2026年)5月期", "date_from": "2025-06-01", "date_to": "2026-05-31"} """ REIWA_OFFSET = 2018 # 令和元年 = 2019年 with get_connection() as conn, conn.cursor() as cur: cur.execute(""" SELECT MIN(entry_date)::date AS min_date, MAX(entry_date)::date AS max_date FROM journal_entries WHERE is_deleted = false """) row = cur.fetchone() if not row or not row["min_date"]: return [] min_date = row["min_date"] max_date = row["max_date"] # 5月期の年度を計算: 決算年(end_year)は entry_date の月が6以上なら翌年、1〜5なら当年 def fiscal_end_year(d): return d.year + 1 if d.month >= 6 else d.year start_fy = fiscal_end_year(min_date) end_fy = fiscal_end_year(max_date) result = [] for fy in range(start_fy, end_fy + 1): # 小さい順(昇順) reiwa = fy - REIWA_OFFSET if reiwa > 0: wareki = f"令和{reiwa}年" elif reiwa == 0: wareki = "平成31/令和元年" else: wareki = f"平成{fy - 1988}年" label = f"{wareki}({fy}年)5月期" result.append({ "label": label, "date_from": f"{fy - 1}-06-01", "date_to": f"{fy}-05-31", }) return result