# backend/app/modules/trial_balance/service.py from decimal import Decimal from typing import Dict, Any, List from app.core.database import get_connection def D(x) -> Decimal: return Decimal(str(x or 0)) # 科目グループ定義(表示順序を制御)- PDFフォーマットに準拠 ACCOUNT_GROUPS = [ # 資産の部 ("現金・預金合計", ["101", "102", "103", "104"]), ("流動資産合計", ["101", "102", "103", "104", "201", "202", "203", "204", "205"]), ("有価証券合計", ["301"]), ("有形固定資産合計", ["401", "402", "403", "404", "405", "406", "407"]), ("無形固定資産合計", ["408"]), ("投資その他の資産合計", ["301", "410"]), ("固定資産合計", ["301", "401", "402", "403", "404", "405", "406", "407", "408", "410"]), ("資産合計", ["101", "102", "103", "104", "201", "202", "203", "204", "205", "301", "401", "402", "403", "404", "405", "406", "407", "408", "410"]), # 負債の部 ("仕入債務合計", ["501"]), ("流動負債合計", ["501", "502", "503", "504", "505", "506", "507", "508"]), ("固定負債合計", ["509"]), ("負債合計", ["501", "502", "503", "504", "505", "506", "507", "508", "509"]), # 資本の部 ("資本金合計", ["601"]), ("利益剰余金合計", ["602"]), ("純資産合計", ["601", "602"]) ] def fetch_trial_balance(date_from: str, date_to: str): with get_connection() as conn, conn.cursor() as cur: # 勘定科目一覧 cur.execute(""" SELECT account_id, account_code, account_name, account_type FROM accounts WHERE is_active = true ORDER BY account_code """) accounts = cur.fetchall() # 科目データを格納 account_data: Dict[str, Dict[str, Any]] = {} for acc in accounts: aid = acc["account_id"] code = acc["account_code"] # 期首残高: opening_balances テーブルの残高 + date_from より前の仕訳 # まず opening_balances テーブルから取得 cur.execute(""" SELECT COALESCE(opening_debit, 0) - COALESCE(opening_credit, 0) AS ob FROM opening_balances WHERE account_id = %s AND fiscal_year = EXTRACT(YEAR FROM %s::date) """, (aid, date_from)) ob_row = cur.fetchone() opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0) # 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_entry_id = l.journal_entry_id WHERE l.account_id = %s AND j.entry_date < %s AND j.is_deleted = false """, (aid, date_from)) opening_from_journals = D(cur.fetchone()["opening"]) # 合計 opening = opening_balance_from_table + opening_from_journals # 当期増減(date_from ~ date_to) cur.execute(""" SELECT COALESCE(SUM(l.debit), 0) AS debit, COALESCE(SUM(l.credit), 0) AS credit FROM journal_lines l JOIN journal_entries j ON j.journal_entry_id = l.journal_entry_id WHERE l.account_id = %s AND j.entry_date BETWEEN %s AND %s AND j.is_deleted = false """, (aid, date_from, date_to)) row = cur.fetchone() debit = D(row["debit"]) credit = D(row["credit"]) closing = opening + debit - credit # 負債・資本は絶対値で表示(見やすくするため) if acc["account_type"] in ("liability", "equity"): opening = abs(opening) closing = abs(closing) account_data[code] = { "account_id": aid, "account_code": code, "account_name": acc["account_name"], "account_type": acc["account_type"], "opening_balance": opening, "debit": debit, "credit": credit, "closing_balance": closing, } # 結果リスト(グループ化) result_accounts: List[Dict[str, Any]] = [] # 既に挿入された合計行を追跡 inserted_totals = set() # 大区分の合計を保存(資産・負債・純資産の総合計) grand_total_closing = D(0) # 個別科目を追加 for code in sorted(account_data.keys()): data = account_data[code] result_accounts.append({ "account_code": data["account_code"], "account_name": data["account_name"], "account_type": data["account_type"], "opening_balance": str(data["opening_balance"]), "debit": str(data["debit"]), "credit": str(data["credit"]), "closing_balance": str(data["closing_balance"]), "ratio": None, # 後で計算 "is_total": False }) # 大区分合計用に総計を積算 grand_total_closing += data["closing_balance"] # 合計行を挿入(グループ定義の順序で) for group_name, group_codes in ACCOUNT_GROUPS: # このグループにまだ合計行を挿入していない if group_name in inserted_totals: continue # 現在の科目がこのグループに含まれる if code not in group_codes: continue # このグループの全科目を取得 group_codes_in_data = [gc for gc in group_codes if gc in account_data] if not group_codes_in_data: continue # グループの最後の科目コードを取得 last_code_in_group = max(group_codes_in_data) # 現在の科目がグループの最後の科目の場合 if code == last_code_in_group: # グループ合計を計算 group_opening = sum(account_data[gc]["opening_balance"] for gc in group_codes_in_data) group_debit = sum(account_data[gc]["debit"] for gc in group_codes_in_data) group_credit = sum(account_data[gc]["credit"] for gc in group_codes_in_data) group_closing = sum(account_data[gc]["closing_balance"] for gc in group_codes_in_data) result_accounts.append({ "account_code": "", "account_name": group_name, "account_type": "", "opening_balance": str(group_opening), "debit": str(group_debit), "credit": str(group_credit), "closing_balance": str(group_closing), "ratio": None, # 後で計算 "is_total": True }) inserted_totals.add(group_name) # 構成比を計算(総資産に対する割合) for acc in result_accounts: closing = D(acc["closing_balance"]) if grand_total_closing != 0: ratio = (closing / grand_total_closing) * 100 acc["ratio"] = f"{ratio:.2f}" else: acc["ratio"] = "0.00" # 総合計を計算 total_opening = sum(data["opening_balance"] for data in account_data.values()) total_debit = sum(data["debit"] for data in account_data.values()) total_credit = sum(data["credit"] for data in account_data.values()) total_closing = sum(data["closing_balance"] for data in account_data.values()) return { "totals": { "opening_balance": str(total_opening), "debit": str(total_debit), "credit": str(total_credit), "closing_balance": str(total_closing), "ratio": "100.00" }, "accounts": result_accounts, "grand_total_closing": str(grand_total_closing) }