This commit is contained in:
admin
2026-03-03 00:01:43 +09:00
parent 3b028b8fc0
commit 068903b933
31 changed files with 3204 additions and 889 deletions

View File

@@ -1,4 +1,5 @@
# backend/app/modules/trial_balance/service.py # backend/app/modules/trial_balance/service.py
# 試算表.pdf フォーマットに完全準拠した試算表サービス
from decimal import Decimal from decimal import Decimal
from typing import Dict, Any, List from typing import Dict, Any, List
from app.core.database import get_connection from app.core.database import get_connection
@@ -6,30 +7,9 @@ from app.core.database import get_connection
def D(x) -> Decimal: def D(x) -> Decimal:
return Decimal(str(x or 0)) return Decimal(str(x or 0))
# 科目グループ定義(表示順序を制御)- PDFフォーマットに準拠 # 除外する科目コード
ACCOUNT_GROUPS = [ EXCLUDED_CODES = {'512', '513', '516'}
# 資産の部
("現金・預金合計", ["101", "102", "103", "104", "105"]),
("売上債権合計", ["201", "202"]),
("有価証券合計", ["301"]),
("棚卸資産合計", ["210"]),
("他流動資産合計", ["203", "204", "205", "206", "207", "208"]),
("流動資産合計", ["101", "102", "103", "104", "105", "201", "202", "203", "204", "205", "206", "207", "208", "210", "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", "105", "201", "202", "203", "204", "205", "206", "207", "208", "210", "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): def fetch_trial_balance(date_from: str, date_to: str):
@@ -44,7 +24,6 @@ def fetch_trial_balance(date_from: str, date_to: str):
""") """)
accounts = cur.fetchall() accounts = cur.fetchall()
# 科目データを格納 # 科目データを格納
account_data: Dict[str, Dict[str, Any]] = {} account_data: Dict[str, Dict[str, Any]] = {}
@@ -52,8 +31,11 @@ def fetch_trial_balance(date_from: str, date_to: str):
aid = acc["account_id"] aid = acc["account_id"]
code = acc["account_code"] code = acc["account_code"]
# 除外科目をスキップ
if code in EXCLUDED_CODES:
continue
# 期首残高: opening_balances テーブルの残高 + date_from より前の仕訳 # 期首残高: opening_balances テーブルの残高 + date_from より前の仕訳
# まず opening_balances テーブルから取得
cur.execute(""" cur.execute("""
SELECT COALESCE(opening_debit, 0) - COALESCE(opening_credit, 0) AS ob SELECT COALESCE(opening_debit, 0) - COALESCE(opening_credit, 0) AS ob
FROM opening_balances FROM opening_balances
@@ -64,7 +46,6 @@ def fetch_trial_balance(date_from: str, date_to: str):
opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0) opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0)
# date_from より前の仕訳による増減 # date_from より前の仕訳による増減
# 修正された仕訳is_latest = falseは除外
cur.execute(""" cur.execute("""
SELECT COALESCE(SUM(l.debit - l.credit), 0) AS opening SELECT COALESCE(SUM(l.debit - l.credit), 0) AS opening
FROM journal_lines l FROM journal_lines l
@@ -76,12 +57,10 @@ def fetch_trial_balance(date_from: str, date_to: str):
AND j.is_latest = true AND j.is_latest = true
""", (aid, date_from)) """, (aid, date_from))
opening_from_journals = D(cur.fetchone()["opening"]) opening_from_journals = D(cur.fetchone()["opening"])
# 合計
opening = opening_balance_from_table + opening_from_journals opening = opening_balance_from_table + opening_from_journals
# 当期増減date_from date_to # 当期増減date_from date_to
# 修正された仕訳is_latest = falseは除外
cur.execute(""" cur.execute("""
SELECT SELECT
COALESCE(SUM(l.debit), 0) AS debit, COALESCE(SUM(l.debit), 0) AS debit,
@@ -97,10 +76,10 @@ def fetch_trial_balance(date_from: str, date_to: str):
row = cur.fetchone() row = cur.fetchone()
debit = D(row["debit"]) debit = D(row["debit"])
credit = D(row["credit"]) credit = D(row["credit"])
closing = opening + debit - credit closing = opening + debit - credit
# 負債・資本は絶対値で表示(見やすくするため) # 負債・資本は絶対値で表示
if acc["account_type"] in ("liability", "equity"): if acc["account_type"] in ("liability", "equity"):
opening = abs(opening) opening = abs(opening)
closing = abs(closing) closing = abs(closing)
@@ -116,182 +95,441 @@ def fetch_trial_balance(date_from: str, date_to: str):
"closing_balance": closing, "closing_balance": closing,
} }
# 結果リスト(グループ化) # =============================================
result_accounts: List[Dict[str, Any]] = [] # ヘルパー関数
# =============================================
# 既に挿入された合計行を追跡 def acct_row(code):
inserted_totals = set() """個別科目の行を返す存在しない場合はNone"""
if code not in account_data:
# 大区分の合計を保存(資産・負債・純資産の総合計) return None
grand_total_closing = D(0) d = account_data[code]
return {
"account_code": d["account_code"],
"account_name": d["account_name"],
"account_type": d["account_type"],
"opening_balance": str(d["opening_balance"]),
"debit": str(d["debit"]),
"credit": str(d["credit"]),
"closing_balance": str(d["closing_balance"]),
"ratio": None,
"is_total": False,
}
# 個別科目を追加 def total_row(name, codes, is_total=True, indent=False):
for code in sorted(account_data.keys()): """合計行を生成"""
data = account_data[code] existing = [c for c in codes if c in account_data]
result_accounts.append({ opening = sum(account_data[c]["opening_balance"] for c in existing) if existing else D(0)
"account_code": data["account_code"], debit = sum(account_data[c]["debit"] for c in existing) if existing else D(0)
"account_name": data["account_name"], credit = sum(account_data[c]["credit"] for c in existing) if existing else D(0)
"account_type": data["account_type"], closing = sum(account_data[c]["closing_balance"] for c in existing) if existing else D(0)
"opening_balance": str(data["opening_balance"]), return {
"debit": str(data["debit"]), "account_code": "",
"credit": str(data["credit"]), "account_name": name,
"closing_balance": str(data["closing_balance"]), "account_type": "",
"ratio": None, # 後で計算 "opening_balance": str(opening),
"is_total": False "debit": str(debit),
}) "credit": str(credit),
"closing_balance": str(closing),
# 大区分合計用に総計を積算 "ratio": None,
grand_total_closing += data["closing_balance"] "is_total": is_total,
"indent": indent,
}
# 利益剰余金602の後に詳細を挿入 def custom_row(name, opening, debit, credit, closing,
if code == "602": is_total=False, indent=False, account_type="", is_pl=False):
# 繰越利益剰余金 = 602の期首残高 """カスタム行を生成"""
retained_earnings_opening = data["opening_balance"] row = {
"account_code": "",
# 当期純損益金額を計算(収益 - 費用) "account_name": name,
# 収益7xxの合計 "account_type": account_type,
revenue_total = D(0) "opening_balance": str(opening),
for acc_code, acc_data in account_data.items(): "debit": str(debit),
if acc_code.startswith("7"): "credit": str(credit),
# 収益は貸方が増加、借方が減少 "closing_balance": str(closing),
revenue_total += acc_data["credit"] - acc_data["debit"] "ratio": None,
"is_total": is_total,
# 費用8xxの合計 "indent": indent,
expense_total = D(0) }
for acc_code, acc_data in account_data.items(): if is_pl:
if acc_code.startswith("8"): row["is_pl"] = True
# 費用は借方が増加、貸方が減少 return row
expense_total += acc_data["debit"] - acc_data["credit"]
# 当期純損益金額 = 収益 - 費用
net_income = revenue_total - expense_total
# 繰越利益剰余金合計 = 期首繰越利益 + 当期純損益
retained_earnings_total = retained_earnings_opening + net_income
# 繰越利益の行を追加
result_accounts.append({
"account_code": "",
"account_name": "繰越利益",
"account_type": "equity",
"opening_balance": str(retained_earnings_opening),
"debit": "0",
"credit": "0",
"closing_balance": str(retained_earnings_opening),
"ratio": None,
"is_total": False,
"indent": True
})
# 当期純損益金額の行を追加
result_accounts.append({
"account_code": "",
"account_name": "当期純損益金額",
"account_type": "equity",
"opening_balance": "0",
"debit": str(expense_total) if expense_total > 0 else "0",
"credit": str(revenue_total) if revenue_total > 0 else "0",
"closing_balance": str(net_income),
"ratio": None,
"is_total": False,
"indent": True
})
# 繰越利益剰余金合計の行を追加
result_accounts.append({
"account_code": "",
"account_name": "繰越利益剰余金合計",
"account_type": "equity",
"opening_balance": str(retained_earnings_opening),
"debit": str(expense_total) if expense_total > 0 else "0",
"credit": str(revenue_total) if revenue_total > 0 else "0",
"closing_balance": str(retained_earnings_total),
"ratio": None,
"is_total": True,
"indent": True
})
# その他利益剰余金合計現在は0
result_accounts.append({
"account_code": "",
"account_name": "その他利益剰余金合計",
"account_type": "equity",
"opening_balance": "0",
"debit": "0",
"credit": "0",
"closing_balance": "0",
"ratio": None,
"is_total": True,
"indent": True
})
# 利益剰余金合計の行を追加
result_accounts.append({
"account_code": "",
"account_name": "利益剰余金合計",
"account_type": "equity",
"opening_balance": str(retained_earnings_opening),
"debit": str(expense_total) if expense_total > 0 else "0",
"credit": str(revenue_total) if revenue_total > 0 else "0",
"closing_balance": str(retained_earnings_total),
"ratio": None,
"is_total": True
})
# 合計行を挿入(グループ定義の順序で) # =============================================
for group_name, group_codes in ACCOUNT_GROUPS: # 結果リスト構築(試算表.pdf の順序に完全準拠)
# このグループにまだ合計行を挿入していない # =============================================
if group_name in inserted_totals: result_accounts: List[Dict[str, Any]] = []
continue added_codes = set() # 追加済みの科目コードを追跡
# 現在の科目がこのグループに含まれる def add(code):
if code not in group_codes: """個別科目を追加"""
continue r = acct_row(code)
if r:
# このグループの全科目を取得 result_accounts.append(r)
group_codes_in_data = [gc for gc in group_codes if gc in account_data] added_codes.add(code)
if not group_codes_in_data:
continue def add_total(name, codes, **kwargs):
"""合計行を追加"""
# グループの最後の科目コードを取得 result_accounts.append(total_row(name, codes, **kwargs))
last_code_in_group = max(group_codes_in_data)
# ========== 資産の部 ==========
# 現在の科目がグループの最後の科目の場合
if code == last_code_in_group: # 現金・預金
# グループ合計を計算 cash_codes = ["101", "102", "103", "104", "105"]
group_opening = sum(account_data[gc]["opening_balance"] for gc in group_codes_in_data) for c in cash_codes:
group_debit = sum(account_data[gc]["debit"] for gc in group_codes_in_data) add(c)
group_credit = sum(account_data[gc]["credit"] for gc in group_codes_in_data) add_total("現金・預金合計", cash_codes)
group_closing = sum(account_data[gc]["closing_balance"] for gc in group_codes_in_data)
# 売上債権
result_accounts.append({ receivable_codes = ["201", "202"]
"account_code": "", for c in receivable_codes:
"account_name": group_name, add(c)
"account_type": "", add_total("売上債権合計", receivable_codes)
"opening_balance": str(group_opening),
"debit": str(group_debit), # 他流動資産
"credit": str(group_credit), other_current_codes = ["203", "204", "205", "206", "207", "208"]
"closing_balance": str(group_closing), for c in other_current_codes:
"ratio": None, # 後で計算 add(c)
"is_total": True add_total("他流動資産合計", other_current_codes)
})
# 有価証券
inserted_totals.add(group_name) securities_codes = ["301"]
for c in securities_codes:
# 構成比を計算(総資産に対する割合) add(c)
for acc in result_accounts: add_total("有価証券合計", securities_codes)
closing = D(acc["closing_balance"])
if grand_total_closing != 0: # 流動資産合計
ratio = (closing / grand_total_closing) * 100 current_asset_codes = cash_codes + receivable_codes + other_current_codes + securities_codes
add_total("流動資産合計", current_asset_codes)
# 有形固定資産
tangible_codes = ["401", "402", "403", "404", "405", "406", "407"]
for c in tangible_codes:
add(c)
add_total("有形固定資産合計", tangible_codes)
# 無形固定資産
intangible_codes = ["408"]
for c in intangible_codes:
add(c)
add_total("無形固定資産合計", intangible_codes)
# 投資その他の資産
investment_codes = ["410"]
for c in investment_codes:
add(c)
add_total("投資その他の資産合計", investment_codes)
# 固定資産合計
fixed_asset_codes = tangible_codes + intangible_codes + investment_codes
add_total("固定資産合計", fixed_asset_codes)
# 資産合計
all_asset_codes = current_asset_codes + fixed_asset_codes
add_total("資産合計", all_asset_codes)
# ========== 負債の部 ==========
# 仕入債務
trade_payable_codes = ["501"]
for c in trade_payable_codes:
add(c)
add_total("仕入債務合計", trade_payable_codes)
# 流動負債その他502-508
current_liability_other = ["502", "503", "504", "505", "506", "507", "508"]
for c in current_liability_other:
add(c)
current_liability_codes = trade_payable_codes + current_liability_other
add_total("流動負債合計", current_liability_codes)
# 固定負債
fixed_liability_codes = ["509"]
for c in fixed_liability_codes:
add(c)
add_total("固定負債合計", fixed_liability_codes)
# 負債合計
all_liability_codes = current_liability_codes + fixed_liability_codes
add_total("負債合計", all_liability_codes)
# ========== 510/511: 独立科目(負債と資本の間) ==========
add("510")
add("511")
# ========== 資本の部 ==========
# 資本金
capital_codes = ["601"]
for c in capital_codes:
add(c)
add_total("資本金合計", capital_codes)
# 繰越利益剰余金602
add("602")
# --- 当期純損益金額の計算 ---
revenue_total = D(0)
for acc_code, acc_data in account_data.items():
if acc_code.startswith("7"):
revenue_total += acc_data["credit"] - acc_data["debit"]
expense_total = D(0)
for acc_code, acc_data in account_data.items():
if acc_code.startswith("8"):
expense_total += acc_data["debit"] - acc_data["credit"]
net_income = revenue_total - expense_total
# 繰越利益剰余金の期首残高
retained_opening = account_data.get("602", {}).get("opening_balance", D(0))
retained_total = retained_opening + net_income
# 繰越利益
result_accounts.append(
custom_row("繰越利益", retained_opening, D(0), D(0), retained_opening,
indent=True, account_type="equity")
)
# 繰越利益剰余金合計
result_accounts.append(
custom_row("繰越利益剰余金合計", retained_opening,
abs(net_income) if net_income < 0 else D(0),
net_income if net_income > 0 else D(0),
retained_total,
is_total=True, indent=True, account_type="equity")
)
# その他利益剰余金合計
result_accounts.append(
custom_row("その他利益剰余金合計", D(0), D(0), D(0), D(0),
is_total=True, indent=True, account_type="equity")
)
# 利益剰余金合計
result_accounts.append(
custom_row("利益剰余金合計", retained_opening,
abs(net_income) if net_income < 0 else D(0),
net_income if net_income > 0 else D(0),
retained_total,
is_total=True, account_type="equity")
)
# 純資産合計
capital_closing = account_data.get("601", {}).get("closing_balance", D(0))
equity_total_closing = capital_closing + retained_total
equity_total_opening = sum(
account_data[c]["opening_balance"] for c in ["601", "602"] if c in account_data
)
result_accounts.append(
custom_row("純資産合計", equity_total_opening, D(0), D(0), equity_total_closing,
is_total=True, account_type="equity")
)
# ========== 損益の部(截図フォーマットに準拠) ==========
# --- P/L 用ヘルパー ---
def pl_row(code, indent=False):
"""P/L個別科目行closing = credit-debit or debit-credit"""
if code not in account_data:
return None
d = account_data[code]
if code.startswith("7"):
pl_closing = d["credit"] - d["debit"]
else:
pl_closing = d["debit"] - d["credit"]
return {
"account_code": d["account_code"],
"account_name": d["account_name"],
"account_type": d["account_type"],
"opening_balance": "0",
"debit": str(d["debit"]),
"credit": str(d["credit"]),
"closing_balance": str(pl_closing),
"ratio": None,
"is_total": False,
"indent": indent,
"is_pl": True,
}
def add_pl(code, indent=False):
r = pl_row(code, indent)
if r:
result_accounts.append(r)
added_codes.add(code)
# === 売上高 ===
add_pl("701")
# 売上高合計
rev_d = account_data.get("701", {})
revenue_val = rev_d.get("credit", D(0)) - rev_d.get("debit", D(0)) if "701" in account_data else D(0)
result_accounts.append(
custom_row("売上高合計", D(0), D(0), revenue_val, revenue_val, is_total=True, is_pl=True)
)
# === 売上原価セクション ===
# 期首商品棚卸高
result_accounts.append(
custom_row("期首商品棚卸高", D(0), D(0), D(0), D(0), indent=True, is_pl=True)
)
# 当期商品仕入高(= 801 仕入高)
if "801" in account_data:
add_pl("801", indent=True)
else:
result_accounts.append(
custom_row("当期商品仕入高", D(0), D(0), D(0), D(0), indent=True, is_pl=True)
)
added_codes.add("801")
# 合計
cogs_d = account_data.get("801", {})
cogs_debit = cogs_d.get("debit", D(0)) if "801" in account_data else D(0)
cogs_credit = cogs_d.get("credit", D(0)) if "801" in account_data else D(0)
cogs_net = cogs_debit - cogs_credit
result_accounts.append(
custom_row("合計", D(0), cogs_debit, cogs_credit, cogs_net,
is_total=True, indent=True, is_pl=True)
)
# 期末商品棚卸高
result_accounts.append(
custom_row("期末商品棚卸高", D(0), D(0), D(0), D(0), indent=True, is_pl=True)
)
# 売上原価
result_accounts.append(
custom_row("売上原価", D(0), cogs_debit, cogs_credit, cogs_net,
is_total=True, indent=True, is_pl=True)
)
# === 売上総損益金額 ===
gross_profit = revenue_val - cogs_net
result_accounts.append(
custom_row("売上総損益金額", D(0), D(0), gross_profit, gross_profit,
is_total=True, is_pl=True)
)
# === 販売管理費(截図の順序に準拠) ===
sga_order = [
"812", "813", "814", "815", # 人件費系
"802", # 外注費
"816", "817", # 交際費・会議費
"803", "804", "805", # 旅費・通信・消耗品
"818", # 新聞図書費
"808", # 支払手数料
"806", # 地代家賃
"819", "820", # 保険料・租税公課
]
# 上記リストにない8xx費用科目を末尾に追加801除外
remaining_8xx = [c for c in sorted(account_data.keys())
if c.startswith("8") and c != "801" and c not in sga_order]
sga_all = sga_order + remaining_8xx
sga_debit_total = D(0)
sga_credit_total = D(0)
sga_closing_total = D(0)
for code in sga_all:
add_pl(code, indent=True)
if code in account_data:
d = account_data[code]
sga_debit_total += d["debit"]
sga_credit_total += d["credit"]
sga_closing_total += d["debit"] - d["credit"]
# 販売管理費計
result_accounts.append(
custom_row("販売管理費計", D(0), sga_debit_total, sga_credit_total,
sga_closing_total, is_total=True, is_pl=True)
)
# === 営業損益金額 ===
operating_profit = gross_profit - sga_closing_total
result_accounts.append(
custom_row("営業損益金額", D(0), D(0), operating_profit, operating_profit,
is_total=True, is_pl=True)
)
# === 営業外収益 ===
non_op_rev_codes = ["702", "709"]
non_op_rev_debit = D(0)
non_op_rev_credit = D(0)
for code in non_op_rev_codes:
add_pl(code, indent=True)
if code in account_data:
d = account_data[code]
non_op_rev_debit += d["debit"]
non_op_rev_credit += d["credit"]
non_op_rev_closing = non_op_rev_credit - non_op_rev_debit
# 営業外収益合計
result_accounts.append(
custom_row("営業外収益合計", D(0), non_op_rev_debit, non_op_rev_credit,
non_op_rev_closing, is_total=True, indent=True, is_pl=True)
)
# 営業外費用合計(現在は該当科目なし)
result_accounts.append(
custom_row("営業外費用合計", D(0), D(0), D(0), D(0),
is_total=True, indent=True, is_pl=True)
)
# === 経常損益金額 ===
ordinary_profit = operating_profit + non_op_rev_closing
result_accounts.append(
custom_row("経常損益金額", D(0), D(0), ordinary_profit, ordinary_profit,
is_total=True, is_pl=True)
)
# === 特別利益合計 ===
result_accounts.append(
custom_row("特別利益合計", D(0), D(0), D(0), D(0),
is_total=True, indent=True, is_pl=True)
)
# === 特別損失合計 ===
result_accounts.append(
custom_row("特別損失合計", D(0), D(0), D(0), D(0),
is_total=True, indent=True, is_pl=True)
)
# === 当期純損益金額 ===
result_accounts.append(
custom_row("当期純損益金額", D(0), D(0), ordinary_profit, ordinary_profit,
is_total=True, is_pl=True)
)
# ========== 未分類科目(上記にない科目を末尾に追加) ==========
for code in sorted(account_data.keys()):
if code not in added_codes:
add(code)
# =============================================
# 構成比を計算
# B/S: 借方残合計ベース
# P/L: 売上高(対売上比)ベース
# =============================================
debit_balance_total = sum(
d["closing_balance"] for d in account_data.values()
if d["closing_balance"] > 0
)
for acc in result_accounts:
closing = D(acc["closing_balance"])
if acc.get("is_pl"):
# P/L: 対売上比
if revenue_val != 0:
ratio = (closing / revenue_val) * 100
acc["ratio"] = f"{ratio:.2f}"
else:
acc["ratio"] = "0.00"
else:
# B/S: 借方残合計ベース
if debit_balance_total != 0:
ratio = (closing / debit_balance_total) * 100
acc["ratio"] = f"{ratio:.2f}" acc["ratio"] = f"{ratio:.2f}"
else: else:
acc["ratio"] = "0.00" acc["ratio"] = "0.00"
# 総合計を計算 # 総合計を計算
total_opening = sum(data["opening_balance"] for data in account_data.values()) total_opening = sum(d["opening_balance"] for d in account_data.values())
total_debit = sum(data["debit"] for data in account_data.values()) total_debit = sum(d["debit"] for d in account_data.values())
total_credit = sum(data["credit"] for data in account_data.values()) total_credit = sum(d["credit"] for d in account_data.values())
total_closing = sum(data["closing_balance"] for data in account_data.values()) total_closing = sum(d["closing_balance"] for d in account_data.values())
result = { result = {
"totals": { "totals": {
@@ -302,7 +540,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
"ratio": "100.00" "ratio": "100.00"
}, },
"accounts": result_accounts, "accounts": result_accounts,
"grand_total_closing": str(grand_total_closing) "grand_total_closing": str(debit_balance_total),
} }
return result return result

View File

@@ -40,6 +40,7 @@ def get_trial_balance(
JOIN accounts a ON a.account_id = l.account_id JOIN accounts a ON a.account_id = l.account_id
WHERE e.is_deleted = false WHERE e.is_deleted = false
AND e.is_latest = true AND e.is_latest = true
AND a.account_code NOT IN ('512', '513', '516')
""" """
else: else:
# 旧系统:排除已修正的记录(使用 parent_entry_id # 旧系统:排除已修正的记录(使用 parent_entry_id
@@ -55,6 +56,7 @@ def get_trial_balance(
JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id JOIN journal_entries e ON e.journal_entry_id = l.journal_entry_id
JOIN accounts a ON a.account_id = l.account_id JOIN accounts a ON a.account_id = l.account_id
WHERE e.is_deleted = false WHERE e.is_deleted = false
AND a.account_code NOT IN ('512', '513', '516')
AND e.journal_entry_id NOT IN ( AND e.journal_entry_id NOT IN (
SELECT parent_entry_id SELECT parent_entry_id
FROM journal_entries FROM journal_entries
@@ -83,7 +85,11 @@ def get_trial_balance(
a.account_name, a.account_name,
a.account_type a.account_type
ORDER BY ORDER BY
a.account_code 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: with get_connection() as conn, conn.cursor() as cur:

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""
フロントエンドの検索結果と実際のDB内容を比較
"""
import sys
sys.path.insert(0, '/app')
from app.db import get_connection
from_date = '2025-06-01'
to_date = '2025-06-30'
account_id = 501
print("=" * 80)
print("【検索条件】")
print(f" 期間: {from_date} {to_date}")
print(f" 科目: {account_id} (費用金)")
print("=" * 80)
with get_connection() as conn:
with conn.cursor() as cur:
# 修正履歴を含まない(最新版本のみ)
print("\n【1】修正履歴を含まないis_latest = true")
sql1 = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.revision_count,
je.is_latest,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.is_latest = true
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.revision_count, je.is_latest
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
"""
cur.execute(sql1, [from_date, to_date, account_id])
results1 = cur.fetchall()
print(f" 件数: {len(results1)}")
for row in results1:
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']})")
# 修正履歴を含む(すべてのバージョン)
print("\n【2】修正履歴を含むすべてのバージョン")
sql2 = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.revision_count,
je.is_latest,
je.original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.revision_count, je.is_latest, je.original_entry_id
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
"""
cur.execute(sql2, [from_date, to_date, account_id])
results2 = cur.fetchall()
print(f" 件数: {len(results2)}")
for row in results2:
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']}, orig={row['original_entry_id']})")
# 修正チェーンの分析
print("\n【3】修正チェーンの詳細分析")
sql3 = """
SELECT
COALESCE(je.original_entry_id, je.journal_entry_id) as chain_id,
COUNT(*) as version_count,
STRING_AGG(DISTINCT je.journal_entry_id::text, ', ') as entry_ids,
STRING_AGG(DISTINCT je.revision_count::text, ', ' ORDER BY je.revision_count::text DESC) as revisions,
MAX(je.revision_count) as max_revision
FROM journal_entries je
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
GROUP BY COALESCE(je.original_entry_id, je.journal_entry_id)
ORDER BY chain_id DESC
"""
cur.execute(sql3, [from_date, to_date, account_id])
chains = cur.fetchall()
print(f" チェーン数: {len(chains)}")
for chain in chains:
print(f" • Chain {chain['chain_id']}: {chain['version_count']} バージョン (IDs: {chain['entry_ids']}, v{chain['revisions']})")
print("\n" + "=" * 80)
print("✓ 分析完了")
print("=" * 80)

107
check_account_19_chain.py Normal file
View File

@@ -0,0 +1,107 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【account_id 19買掛金の修正チェーン分析】")
print("=" * 80)
from_date = "2025-06-01"
to_date = "2025-06-30"
account_id = 19
with get_connection() as conn:
with conn.cursor() as cur:
# Step 1: account_id 19 を含む仕訳を全て取得
print("\n【Step 1】account_id 19 を含むすべての仕訳(期間内)")
cur.execute("""
SELECT DISTINCT je.journal_entry_id, je.entry_date, je.description,
je.revision_count, je.is_latest, je.original_entry_id
FROM journal_entries je
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
""", [from_date, to_date, account_id])
all_entries = cur.fetchall()
print(f"件数: {len(all_entries)}")
for row in all_entries:
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description'][:50]}... (v{row['revision_count']}, latest={row['is_latest']}, orig={row['original_entry_id']})")
# Step 2: 修正チェーンの分析
print("\n【Step 2】修正チェーンの詳細")
cur.execute("""
SELECT
COALESCE(je.original_entry_id, je.journal_entry_id) as chain_id,
array_agg(je.journal_entry_id ORDER BY je.revision_count) as entry_ids,
array_agg(je.revision_count ORDER BY je.revision_count) as revisions,
array_agg(je.is_latest ORDER BY je.revision_count) as is_latest_flags,
MAX(je.revision_count) as max_revision,
COUNT(*) as version_count
FROM journal_entries je
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
GROUP BY COALESCE(je.original_entry_id, je.journal_entry_id)
ORDER BY chain_id DESC
""", [from_date, to_date, account_id])
chains = cur.fetchall()
print(f"修正チェーン数: {len(chains)}")
for chain in chains:
print(f"\n chain_id {chain['chain_id']}:")
print(f" • エントリID: {chain['entry_ids']}")
print(f" • リビジョン: {chain['revisions']}")
print(f" • 最新フラグ: {chain['is_latest_flags']}")
print(f" • バージョン数: {chain['version_count']}, 最大v{chain['max_revision']}")
# Step 3: 修正履歴を含まない場合と含む場合の件数比較
print("\n【Step 3】修正履歴フィルター適用の比較")
# without include_history
cur.execute("""
SELECT COUNT(*) as cnt
FROM journal_entries je
WHERE je.is_deleted = false
AND je.is_latest = true
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
""", [from_date, to_date, account_id])
result_without = cur.fetchone()
print(f" 修正履歴を含まないis_latest=true: {result_without['cnt']}")
# with include_history
cur.execute("""
SELECT COUNT(*) as cnt
FROM journal_entries je
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
""", [from_date, to_date, account_id])
result_with = cur.fetchone()
print(f" 修正履歴を含む(すべてのバージョン): {result_with['cnt']}")
print(f" 差分: {result_with['cnt'] - result_without['cnt']} 件(古いバージョン)")
print("\n" + "=" * 80)

69
check_accounts_105_502.py Normal file
View File

@@ -0,0 +1,69 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【科目 105 小口現金 と 502 未払金 の確認】")
print("=" * 80)
with get_connection() as conn:
with conn.cursor() as cur:
# 科目情報を確認
print("\n【科目情報】")
cur.execute("""
SELECT account_id, account_code, account_name, account_type
FROM accounts
WHERE account_id IN (105, 502)
ORDER BY account_id
""")
accounts = cur.fetchall()
for acc in accounts:
print(f" • ID {acc['account_id']}: {acc['account_code']} {acc['account_name']} ({acc['account_type']})")
# 105 を含む journal_lines の件数
print("\n【105 小口現金 を含む仕訳行】")
cur.execute("""
SELECT COUNT(*) as cnt
FROM journal_lines
WHERE account_id = 105
""")
result = cur.fetchone()
print(f" 件数: {result['cnt']}")
if result['cnt'] > 0:
# 詳細を表示
print("\n【詳細例(最初の 10 件)】")
cur.execute("""
SELECT
jl.journal_line_id,
jl.journal_entry_id,
je.entry_date,
je.description,
jl.debit,
jl.credit
FROM journal_lines jl
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
WHERE jl.account_id = 105
ORDER BY je.entry_date DESC
LIMIT 10
""")
for row in cur.fetchall():
debit_display = f"借方 {row['debit']}" if row['debit'] > 0 else ""
credit_display = f"貸方 {row['credit']}" if row['credit'] > 0 else ""
side = (debit_display + " " + credit_display).strip()
print(f" • ID {row['journal_line_id']}: {row['entry_date']} - {row['description'][:40]}... ({side})")
# 102 と比較(参考用)
print("\n【502 未払金 の現在の利用状況】")
cur.execute("""
SELECT COUNT(*) as cnt
FROM journal_lines
WHERE account_id = 502
""")
result502 = cur.fetchone()
print(f" 件数: {result502['cnt']}")
print("\n" + "=" * 80)

View File

@@ -0,0 +1,50 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【期間内2025-06-0106-30のすべての仕訳と科目】")
print("=" * 80)
from_date = "2025-06-01"
to_date = "2025-06-30"
with get_connection() as conn:
with conn.cursor() as cur:
# 期間内のすべての仕訳
cur.execute("""
SELECT je.journal_entry_id, je.entry_date, je.description
FROM journal_entries je
WHERE je.is_deleted = false
AND je.is_latest = true
AND je.entry_date >= %s
AND je.entry_date <= %s
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
LIMIT 10
""", [from_date, to_date])
rows = cur.fetchall()
print(f"\n期間内の仕訳(最新版のみ):{len(rows)}")
for i, row in enumerate(rows, 1):
print(f"\n{i}. 仕訳 ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']}")
# この仕訳の科目を表示
cur.execute("""
SELECT
a.account_id,
a.account_code,
a.account_name,
jl.debit,
jl.credit
FROM journal_lines jl
JOIN accounts a ON jl.account_id = a.account_id
WHERE jl.journal_entry_id = %s
ORDER BY a.account_id
""", [row['journal_entry_id']])
for line in cur.fetchall():
print(f" • ID {line['account_id']} ({line['account_code']}) {line['account_name']}: 借方 {line['debit']}, 貸方 {line['credit']}")
print("\n" + "=" * 80)

View File

@@ -0,0 +1,71 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【すべての journal_entry の修正歴チェーン分析】")
print("=" * 80)
with get_connection() as conn:
with conn.cursor() as cur:
# すべてのチェーンを取得
cur.execute("""
SELECT
COALESCE(je.original_entry_id, je.journal_entry_id) as chain_id,
array_agg(DISTINCT je.journal_entry_id ORDER BY je.journal_entry_id) as all_entry_ids,
array_agg(DISTINCT je.revision_count ORDER BY je.revision_count DESC) as all_revisions,
COUNT(*) as total_versions,
COUNT(CASE WHEN je.is_latest = true THEN 1 END) as latest_count,
COUNT(CASE WHEN je.is_latest = false THEN 1 END) as old_count,
MAX(je.revision_count) as max_revision
FROM journal_entries je
WHERE je.is_deleted = false
GROUP BY COALESCE(je.original_entry_id, je.journal_entry_id)
HAVING COUNT(*) > 1 OR MAX(je.revision_count) > 1
ORDER BY MAX(je.revision_count) DESC
LIMIT 20
""")
chains = cur.fetchall()
print(f"\n修正チェーン数: {len(chains)}")
if chains:
print("\n📋 詳細:")
for i, chain in enumerate(chains, 1):
print(f"\n{i}. Chain ID {chain['chain_id']}:")
print(f" • 全 entry_id: {chain['all_entry_ids']}")
print(f" • リビジョン: {chain['all_revisions']}")
print(f" • 総バージョン数: {chain['total_versions']}")
print(f" • 最新版本: {chain['latest_count']}, 古いバージョン: {chain['old_count']}")
print(f" • 最大リビジョン: v{chain['max_revision']}")
else:
print("\n⚠️ 修正歴チェーン(複数バージョン)が見つかりません")
# 古いバージョンが存在するか直接確認
print("\n" + "=" * 80)
print("【古いバージョンis_latest = falseの存在確認】")
cur.execute("""
SELECT COUNT(*) as old_version_count,
COUNT(DISTINCT COALESCE(original_entry_id, journal_entry_id)) as old_chain_count
FROM journal_entries
WHERE is_deleted = false AND is_latest = false
""")
old_result = cur.fetchone()
print(f"\n古いバージョン総数: {old_result['old_version_count']}")
print(f"古いバージョンを含むチェーン数: {old_result['old_chain_count']}")
if old_result['old_version_count'] > 0:
print("\n📝 古いバージョンの例(最初の 5 個):")
cur.execute("""
SELECT je.journal_entry_id, je.entry_date, je.description,
je.revision_count, je.original_entry_id
FROM journal_entries je
WHERE je.is_deleted = false AND je.is_latest = false
ORDER BY je.journal_entry_id DESC
LIMIT 5
""")
for row in cur.fetchall():
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - v{row['revision_count']} (orig={row['original_entry_id']})")
print("\n" + "=" * 80)

69
check_api_fix.py Normal file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""Test if the trial-balance API fix is applied"""
import requests
import json
url = 'http://127.0.0.1:18080/trial-balance'
params = {
'fiscal_year': 2025,
'date_from': '2025-06-01',
'date_to': '2025-12-31'
}
print("🔍 Testing API...")
print(f"URL: {url}")
print()
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
if isinstance(data, dict) and 'accounts' in data:
accounts = data['accounts']
# Find the target row
target = [row for row in accounts if row.get('account_name') == '繰越利益剰余金合計']
if target:
row = target[0]
print("✓ Found: 繰越利益剰余金合計")
print(f" debit: {row.get('debit')}")
print(f" credit: {row.get('credit')}")
print()
print("Expected (after fix):")
print(" debit: 0")
print(" credit: 7967855")
print()
# Convert to float for comparison (handles both "0" and "0.00")
try:
debit_correct = float(row.get('debit', '0')) == 0.0
credit_correct = float(row.get('credit', '0')) == 7967855.0
except (ValueError, TypeError):
debit_correct = False
credit_correct = False
if debit_correct and credit_correct:
print("✅✅✅ SUCCESS!")
print("✅ API is now returning CORRECT values!")
print("✅ The fix has been successfully applied and verified!")
else:
print("❌ Values still incorrect")
if not debit_correct:
print(f" - debit is {row.get('debit')}, expected 0")
if not credit_correct:
print(f" - credit is {row.get('credit')}, expected 7967855")
else:
print("❌ Cannot find 繰越利益剰余金合計 row")
print("\nLast 5 rows:")
for row in accounts[-5:]:
print(f" {row.get('account_name')}: debit={row.get('debit')}, credit={row.get('credit')}")
else:
print("Unexpected response structure")
print(json.dumps(data, indent=2, ensure_ascii=False)[:200])
except Exception as e:
print(f"❌ Error: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()

112
check_api_query.py Normal file
View File

@@ -0,0 +1,112 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【実際に API が使用している SQL クエリを再現】")
print("=" * 80)
from_date = "2025-06-01"
to_date = "2025-06-30"
account_id = 501
include_history = False
with get_connection() as conn:
with conn.cursor() as cur:
# フィールド存在チェック
cur.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'journal_entries'
AND column_name IN ('is_latest', 'revision_count', 'original_entry_id')
""")
existing_fields = {row['column_name'] for row in cur.fetchall()}
print(f"\n📋 DB フィールド: {existing_fields}")
if 'revision_count' in existing_fields and 'is_latest' in existing_fields:
if include_history:
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.fiscal_year,
je.revision_count,
je.is_latest,
je.original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total,
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ') as tax_rates
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
"""
else:
sql = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.fiscal_year,
je.revision_count,
je.is_latest,
je.original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total,
STRING_AGG(DISTINCT CASE WHEN jl.tax_rate IS NOT NULL THEN jl.tax_rate::text END, ', ') as tax_rates
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.is_latest = true
"""
params = []
# 期間フ
sql += " AND je.entry_date >= %s"
params.append(from_date)
sql += " AND je.entry_date <= %s"
params.append(to_date)
# account_id フィルター
if account_id:
sql += """ AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)"""
params.append(account_id)
sql += " GROUP BY je.journal_entry_id, je.entry_date, je.description, je.fiscal_year, je.revision_count, je.is_latest, je.original_entry_id"
sql += " ORDER BY je.entry_date DESC, je.journal_entry_id DESC"
print(f"\n【SQL】")
print(sql)
print(f"\n【パラメータ】{params}")
cur.execute(sql, params)
rows = cur.fetchall()
print(f"\n【結果】{len(rows)}")
for i, row in enumerate(rows, 1):
print(f"\n{i}. ID {row['journal_entry_id']}")
print(f" 日付: {row['entry_date']}")
print(f" 摘要: {row['description']}")
print(f" 借方: {row['debit_total']}, 貸方: {row['credit_total']}")
# account_id 501 のレコードを持つ仕訳を確認
print("\n" + "=" * 80)
print("【仕訳に含まれる科目の内訳】")
if rows:
for row in rows[:1]: # 最初のレコードが 501 を含むか
print(f"\n仕訳 ID {row['journal_entry_id']} の行科目:")
cur.execute("""
SELECT account_id, account_name, debit, credit
FROM journal_lines jl
JOIN accounts a ON jl.account_id = a.account_id
WHERE jl.journal_entry_id = %s
""", [row['journal_entry_id']])
for line in cur.fetchall():
print(f"{line['account_id']} {line['account_name']}: 借方 {line['debit']}, 貸方 {line['credit']}")
print("\n" + "=" * 80)

View File

@@ -0,0 +1,53 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【Chain 140 の各バージョンの科目内訳】")
print("=" * 80)
chain_id = 140
entry_ids = [140, 438, 461]
with get_connection() as conn:
with conn.cursor() as cur:
for entry_id in entry_ids:
cur.execute("""
SELECT je.journal_entry_id, je.revision_count, je.is_latest, je.entry_date
FROM journal_entries je
WHERE je.journal_entry_id = %s
""", [entry_id])
entry = cur.fetchone()
print(f"\n【ID {entry_id} (v{entry['revision_count']}, latest={entry['is_latest']})】")
print(f"日付: {entry['entry_date']}")
cur.execute("""
SELECT
a.account_id,
a.account_code,
a.account_name,
COALESCE(jl.debit, 0) as debit,
COALESCE(jl.credit, 0) as credit
FROM journal_lines jl
JOIN accounts a ON jl.account_id = a.account_id
WHERE jl.journal_entry_id = %s
ORDER BY a.account_id
""", [entry_id])
lines = cur.fetchall()
print(f" 行数: {len(lines)}")
for line in lines:
debit_display = f"借方 {line['debit']}" if line['debit'] > 0 else ""
credit_display = f"貸方 {line['credit']}" if line['credit'] > 0 else ""
side = (debit_display + " " + credit_display).strip()
marker = " ← 古いバージョンにない科目" if line['account_id'] == 19 and entry_id == 461 else ""
print(f"{line['account_id']} ({line['account_code']}) {line['account_name']}: {side}{marker}")
print("\n" + "=" * 80)
print("【結論】")
print("修正前バージョンと修正後バージョンで科目構成が異なります。")
print("古いバージョンには account_id 19買掛金がないため、")
print("account_id 19 でフィルターした場合、最新バージョンのみが返されます。")
print("=" * 80)

75
check_chain_140_detail.py Normal file
View File

@@ -0,0 +1,75 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【Chain ID 140 の詳細分析】")
print("=" * 80)
chain_id = 140
with get_connection() as conn:
with conn.cursor() as cur:
# チェーン 140 内のすべてのバージョン
cur.execute("""
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.revision_count,
je.is_latest,
je.original_entry_id
FROM journal_entries je
WHERE (je.journal_entry_id = %s OR je.original_entry_id = %s)
AND je.is_deleted = false
ORDER BY je.revision_count ASC
""", [chain_id, chain_id])
versions = cur.fetchall()
print(f"\nチェーン内のバージョン: {len(versions)}")
for row in versions:
print(f"\n • ID {row['journal_entry_id']} (v{row['revision_count']}):")
print(f" - 日付: {row['entry_date']}")
print(f" - 摘要: {row['description'][:60]}...")
print(f" - 最新: {row['is_latest']}")
print(f" - オリジナル: {row['original_entry_id']}")
# 各バージョンで account_id 19 を持つか確認
print("\n【各バージョンで account_id 19 を持つか確認】")
for row in versions:
cur.execute("""
SELECT COUNT(*) as cnt
FROM journal_lines
WHERE journal_entry_id = %s AND account_id = 19
""", [row['journal_entry_id']])
has_account = cur.fetchone()
print(f" ID {row['journal_entry_id']}: account_id 19を含む = {has_account['cnt'] > 0}")
# 期間内2025-06-0106-30で、account_id 19を含むすべてのバージョン
print("\n【期間内 + account_id 19 フィルター】")
cur.execute("""
SELECT
je.journal_entry_id,
je.entry_date,
je.revision_count,
je.is_latest
FROM journal_entries je
WHERE (je.journal_entry_id = %s OR je.original_entry_id = %s)
AND je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = 19
)
ORDER BY je.revision_count ASC
""", [chain_id, chain_id, '2025-06-01', '2025-06-30'])
filtered = cur.fetchall()
print(f"\n期間内 + account_id 19 フィルター: {len(filtered)}")
for row in filtered:
print(f" • ID {row['journal_entry_id']} (v{row['revision_count']}, latest={row['is_latest']}): {row['entry_date']}")
print("\n" + "=" * 80)

85
check_db_records.py Normal file
View File

@@ -0,0 +1,85 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
from_date = "2025-06-01"
to_date = "2025-06-30"
account_id = 501
print("=" * 80)
print("【検索条件】")
print(f" 期間: {from_date} {to_date}")
print(f" 科目: {account_id} (費用金)")
print("=" * 80)
with get_connection() as conn:
with conn.cursor() as cur:
# ステップ 1: journal_lines に account_id 501 のレコードがあるか
print("\n【ステップ 0】account_id 501 を持つ journal_lines")
sql0 = "SELECT COUNT(*) as cnt FROM journal_lines WHERE account_id = %s"
cur.execute(sql0, [account_id])
result0 = cur.fetchone()
print(f" 全体件数: {result0['cnt']}")
# ステップ 2: journal_lines に account_id 501 で期間限定のレコードがあるか
print("\n【ステップ 0.5】account_id 501 で期間限定のレコード")
sql0_5 = """
SELECT COUNT(*) as cnt FROM journal_lines jl
WHERE jl.account_id = %s
AND jl.journal_entry_id IN (
SELECT journal_entry_id FROM journal_entries
WHERE entry_date >= %s AND entry_date <= %s
)
"""
cur.execute(sql0_5, [account_id, from_date, to_date])
result0_5 = cur.fetchone()
print(f" 期間内件数: {result0_5['cnt']}")
# ステップ 3: その journal_entry_id のリスト
print("\n【ステップ 1】該当する journal_entry_id のリスト")
sql1 = """
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
AND journal_entry_id IN (
SELECT journal_entry_id FROM journal_entries
WHERE entry_date >= %s AND entry_date <= %s
)
"""
cur.execute(sql1, [account_id, from_date, to_date])
entry_ids = [row['journal_entry_id'] for row in cur.fetchall()]
print(f" 該当 journal_entry_id: {entry_ids}")
if entry_ids:
print("\n【ステップ 2】これらの仕訳の詳細")
placeholders = ','.join(['%s'] * len(entry_ids))
sql2 = f"""
SELECT
journal_entry_id, entry_date, description, revision_count, is_latest
FROM journal_entries
WHERE journal_entry_id IN ({placeholders})
"""
cur.execute(sql2, entry_ids)
for row in cur.fetchall():
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']})")
# オリジナルのクエリ
print("\n【ステップ 3】修正履歴を含まないis_latest = true")
sql3 = """
SELECT COUNT(*) as cnt FROM journal_entries je
WHERE je.is_deleted = false
AND je.is_latest = true
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
"""
cur.execute(sql3, [from_date, to_date, account_id])
result3 = cur.fetchone()
print(f" 件数: {result3['cnt']}")
print("\n" + "=" * 80)

48
check_revisions.py Normal file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
import psycopg2
from psycopg2.extras import RealDictCursor
import os
# 接続設定
conn = psycopg2.connect(
user=os.getenv('DB_USER', 'postgres'),
password=os.getenv('DB_PASSWORD', 'postgres'),
host='localhost',
port='5432',
database=os.getenv('DB_NAME', 'accounting_db')
)
cur = conn.cursor(cursor_factory=RealDictCursor)
# 修正版本revision_count > 1があるか確認
cur.execute("""
SELECT COUNT(*) as total,
COUNT(CASE WHEN is_latest = true THEN 1 END) as latest_count,
COUNT(CASE WHEN revision_count > 1 THEN 1 END) as revised_count,
MAX(revision_count) as max_revision
FROM journal_entries
WHERE is_deleted = false
""")
result = cur.fetchone()
print("📊 修正版本統計")
print(f" • 全記録: {result['total']}")
print(f" • 最新版本: {result['latest_count']}")
print(f" • 修正版本: {result['revised_count']}")
print(f" • 最大改版數: {result['max_revision']}")
# 修正版本の例を表示
if result['revised_count'] > 0:
print("\n📝 修正版本の例(最初の 5 件):")
cur.execute("""
SELECT journal_entry_id, entry_date, description, revision_count, is_latest
FROM journal_entries
WHERE revision_count > 1 AND is_deleted = false
ORDER BY revision_count DESC, journal_entry_id DESC
LIMIT 5
""")
for row in cur.fetchall():
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']})")
cur.close()
conn.close()

131
check_search_results.py Normal file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python3
import os
import sys
import psycopg2
from psycopg2.extras import RealDictCursor
from datetime import date
# 接続設定
try:
conn = psycopg2.connect(
user=os.getenv('DB_USER', 'postgres'),
password=os.getenv('DB_PASSWORD', 'postgres'),
host='postgres',
port='5432',
database=os.getenv('DB_NAME', 'accounting_db')
)
except:
# ローカル接続を試みる
conn = psycopg2.connect(
user='postgres',
password='postgres',
host='localhost',
port='5432',
database='accounting_db'
)
cur = conn.cursor(cursor_factory=RealDictCursor)
# 検索条件
from_date = '2025-06-01'
to_date = '2025-06-30'
account_id = 501 # 費用金
# 条件1: 修正履歴を含まない(最新版本のみ)
print("=" * 80)
print("【検索条件】")
print(f" 期間: {from_date} {to_date}")
print(f" 科目: {account_id}")
print("=" * 80)
print("\n【1】修正履歴を含まないis_latest = true")
sql1 = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.revision_count,
je.is_latest,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.is_latest = true
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.revision_count, je.is_latest
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
"""
cur.execute(sql1, [from_date, to_date, account_id])
results1 = cur.fetchall()
print(f" 件数: {len(results1)}")
for row in results1:
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']})")
print("\n【2】修正履歴を含むすべてのバージョン")
sql2 = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.revision_count,
je.is_latest,
je.original_entry_id,
COALESCE(SUM(jl.debit), 0) as debit_total,
COALESCE(SUM(jl.credit), 0) as credit_total
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
AND je.journal_entry_id IN (
SELECT DISTINCT journal_entry_id
FROM journal_lines
WHERE account_id = %s
)
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.revision_count, je.is_latest, je.original_entry_id
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
"""
cur.execute(sql2, [from_date, to_date, account_id])
results2 = cur.fetchall()
print(f" 件数: {len(results2)}")
for row in results2:
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']}, orig={row['original_entry_id']})")
print("\n【3】同じ日付・科目の修正版本の詳細情報")
sql3 = """
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
je.revision_count,
je.is_latest,
je.original_entry_id,
COUNT(DISTINCT jl.account_id) as account_count
FROM journal_entries je
LEFT JOIN journal_lines jl ON je.journal_entry_id = jl.journal_entry_id
WHERE je.is_deleted = false
AND je.entry_date >= %s
AND je.entry_date <= %s
GROUP BY je.journal_entry_id, je.entry_date, je.description, je.revision_count, je.is_latest, je.original_entry_id
ORDER BY je.entry_date DESC, je.journal_entry_id DESC
LIMIT 10
"""
cur.execute(sql3, [from_date, to_date])
results3 = cur.fetchall()
print(f" 同じ期間内のすべての仕訳最初の10件:")
for row in results3:
print(f" • ID {row['journal_entry_id']}: {row['entry_date']} - {row['description']} (v{row['revision_count']}, latest={row['is_latest']}, orig={row['original_entry_id']}, accts={row['account_count']})")
cur.close()
conn.close()
print("\n" + "=" * 80)
print("✓ 分析完了")
print("=" * 80)

View File

@@ -10,6 +10,8 @@ services:
- .env - .env
ports: ports:
- "18080:18080" - "18080:18080"
volumes:
- ./frontend:/app/frontend:ro
networks: networks:
- njts_net - njts_net

BIN
docs/File.PDF Normal file

Binary file not shown.

BIN
docs/File0001.PDF Normal file

Binary file not shown.

BIN
docs/試算表.pdf Normal file

Binary file not shown.

View File

@@ -44,6 +44,7 @@
.account-search-container { .account-search-container {
position: relative; position: relative;
width: 100%; width: 100%;
box-sizing: border-box;
} }
.account-search-input { .account-search-input {
@@ -52,6 +53,7 @@
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
box-sizing: border-box;
} }
.account-search-input:focus { .account-search-input:focus {
@@ -84,6 +86,9 @@
cursor: pointer; cursor: pointer;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
font-size: 14px; font-size: 14px;
text-align: left;
display: block;
width: 100%;
} }
.account-option:hover, .account-option:hover,
@@ -94,11 +99,14 @@
.account-option-strong { .account-option-strong {
font-weight: bold; font-weight: bold;
color: #333; color: #333;
display: inline;
margin-right: 6px;
} }
.account-option-light { .account-option-light {
color: #999; color: #555;
font-size: 12px; font-size: 14px;
display: inline;
} }
</style> </style>
</head> </head>
@@ -241,43 +249,81 @@
const container = inputElement.closest(".account-search-container"); const container = inputElement.closest(".account-search-container");
const dropdown = container.querySelector(".account-dropdown"); const dropdown = container.querySelector(".account-dropdown");
inputElement.addEventListener("input", (e) => { // 科目選択履歴を保存journal-entry.htmlと共通のlocalStorageキー
const searchText = e.target.value.toLowerCase().trim(); function saveAccountToRecentEdit(accountId) {
if (!accountId) return;
let recent = JSON.parse(localStorage.getItem("recentAccounts") || "[]");
recent = recent.filter((id) => id !== accountId);
recent.unshift(accountId);
if (recent.length > 20) recent = recent.slice(0, 20);
localStorage.setItem("recentAccounts", JSON.stringify(recent));
}
if (!searchText) { function makeLbl(text) {
dropdown.classList.remove("active"); const lbl = document.createElement("div");
return; lbl.style.cssText = "padding:3px 12px;font-size:11px;color:#888;background:#f5f5f5;font-weight:bold;border-bottom:1px solid #e0e0e0;";
} lbl.textContent = text;
return lbl;
}
// 过滤账户 // 显示科目下拉列表的函数
const filtered = accounts.filter((acc) => { function showAccountList(searchText = "") {
const code = acc.account_code.toLowerCase(); const isFiltered = searchText.length > 0;
const name = acc.account_name.toLowerCase(); let filtered = accounts;
return code.includes(searchText) || name.includes(searchText); if (isFiltered) {
}); filtered = accounts.filter((acc) => {
const code = acc.account_code.toLowerCase();
// 生成下拉建议 const name = acc.account_name.toLowerCase();
dropdown.innerHTML = ""; return code.includes(searchText) || name.includes(searchText);
if (filtered.length === 0) {
dropdown.innerHTML = `<div style="padding: 8px 12px; color: #999;">該当する科目がありません</div>`;
} else {
filtered.slice(0, 20).forEach((acc) => {
const option = document.createElement("div");
option.className = "account-option";
option.innerHTML = `
<span class="account-option-strong">${acc.account_code}</span>
<span class="account-option-light">${acc.account_name}</span>
`;
option.addEventListener("click", () => {
inputElement.value = `${acc.account_code} ${acc.account_name}`;
inputElement.dataset.accountId = acc.account_id;
dropdown.classList.remove("active");
});
dropdown.appendChild(option);
}); });
} }
dropdown.innerHTML = "";
function appendOpt(acc) {
const option = document.createElement("div");
option.className = "account-option";
option.innerHTML = `<span class="account-option-strong">${acc.account_code}</span><span class="account-option-light">${acc.account_name}</span>`;
option.addEventListener("click", () => {
inputElement.value = `${acc.account_code} ${acc.account_name}`;
inputElement.dataset.accountId = acc.account_id;
dropdown.classList.remove("active");
saveAccountToRecentEdit(acc.account_id);
});
dropdown.appendChild(option);
}
if (filtered.length === 0) {
dropdown.innerHTML = `<div style="padding: 8px 12px; color: #999;">該当する科目がありません</div>`;
} else if (isFiltered) {
const sorted = filtered.slice().sort((a, b) => a.account_code.localeCompare(b.account_code));
sorted.slice(0, 50).forEach(appendOpt);
} else {
const recentIds = JSON.parse(localStorage.getItem("recentAccounts") || "[]");
const recentItems = recentIds.map((id) => filtered.find((a) => a.account_id === id)).filter(Boolean);
const recentSet = new Set(recentIds);
const otherItems = filtered
.filter((a) => !recentSet.has(a.account_id))
.sort((a, b) => a.account_code.localeCompare(b.account_code));
if (recentItems.length > 0) {
dropdown.appendChild(makeLbl("⭐ 最近使った科目"));
recentItems.forEach(appendOpt);
const sep = document.createElement("div");
sep.style.cssText = "border-top:1px solid #e8e8e8;margin:2px 0;";
dropdown.appendChild(sep);
}
dropdown.appendChild(makeLbl("📋 すべての科目"));
otherItems.slice(0, 50).forEach(appendOpt);
}
dropdown.classList.add("active"); dropdown.classList.add("active");
}
// 入力時:フィルター
inputElement.addEventListener("input", (e) => {
const searchText = e.target.value.toLowerCase().trim();
showAccountList(searchText);
}); });
// クリック以外の場所のドロップダウンを非表示 // クリック以外の場所のドロップダウンを非表示
@@ -287,11 +333,9 @@
} }
}); });
// フォーカス時にドロップダウンを表示(既存値がある場合) // フォーカス時:すべての科目を表示
inputElement.addEventListener("focus", () => { inputElement.addEventListener("focus", () => {
if (inputElement.value) { showAccountList(inputElement.value.toLowerCase().trim());
dropdown.classList.add("active");
}
}); });
} }
@@ -386,13 +430,20 @@
if (line) { if (line) {
// Find the account and populate the input // Find the account and populate the input
const account = accounts.find(a => a.account_id === line.account_id); const account = accounts.find(
(a) => a.account_id === line.account_id,
);
if (account) { if (account) {
accountInput.value = `${account.account_code} ${account.account_name}`; accountInput.value = `${account.account_code} ${account.account_name}`;
accountInput.dataset.accountId = account.account_id; accountInput.dataset.accountId = account.account_id;
} }
tr.querySelectorAll("input")[1].value = line.debit || ""; // type="number" のみを取得
tr.querySelectorAll("input")[2].value = line.credit || ""; const numberInputs = tr.querySelectorAll("input[type='number']");
// Decimal 値を数値に変換してから代入
const debitVal = Number(line.debit) || 0;
const creditVal = Number(line.credit) || 0;
numberInputs[0].value = debitVal > 0 ? String(debitVal) : "";
numberInputs[1].value = creditVal > 0 ? String(creditVal) : "";
} }
// 入力時に合計を自動更新 // 入力時に合計を自動更新
@@ -414,8 +465,26 @@
let d = 0, let d = 0,
c = 0; c = 0;
document.querySelectorAll("#linesTable tbody tr").forEach((tr) => { document.querySelectorAll("#linesTable tbody tr").forEach((tr) => {
d += Number(tr.querySelectorAll("input")[0].value || 0); // type="number" のみを取得(科目検索入力框を除外)
c += Number(tr.querySelectorAll("input")[1].value || 0); const numberInputs = tr.querySelectorAll("input[type='number']");
if (numberInputs.length >= 2) {
const debitStr = numberInputs[0]?.value;
const creditStr = numberInputs[1]?.value;
// 空または 0 を含む場合は 0、そうでなければ数値に変換
const debitVal =
debitStr && debitStr.trim()
? Number(debitStr.toString().replace(/,/g, ""))
: 0;
const creditVal =
creditStr && creditStr.trim()
? Number(creditStr.toString().replace(/,/g, ""))
: 0;
// NaN チェック
if (!isNaN(debitVal)) d += debitVal;
if (!isNaN(creditVal)) c += creditVal;
}
}); });
document.getElementById("debitTotal").textContent = d.toLocaleString(); document.getElementById("debitTotal").textContent = d.toLocaleString();
document.getElementById("creditTotal").textContent = c.toLocaleString(); document.getElementById("creditTotal").textContent = c.toLocaleString();
@@ -429,13 +498,28 @@
result.textContent = ""; result.textContent = "";
result.className = ""; result.className = "";
const entryDate = document.getElementById("entryDate").value; const entryDateInput = document.getElementById("entryDate");
const entryDate = entryDateInput.value;
const desc = document.getElementById("description").value; const desc = document.getElementById("description").value;
if (!entryDate || !desc) { if (entryDateInput.validity.badInput) {
showError(
"日付が正しくありません。存在しない日付が入力されています11月31日は無効です",
);
return;
}
if (!entryDate && !desc) {
showError("日付と摘要は必須です"); showError("日付と摘要は必須です");
return; return;
} }
if (!entryDate) {
showError("日付を入力してください");
return;
}
if (!desc) {
showError("摘要を入力してください");
return;
}
const lines = []; const lines = [];
let d = 0, let d = 0,
@@ -444,8 +528,9 @@
document.querySelectorAll("#linesTable tbody tr").forEach((tr) => { document.querySelectorAll("#linesTable tbody tr").forEach((tr) => {
const accountInput = tr.querySelector(".account-search-input"); const accountInput = tr.querySelector(".account-search-input");
const accountId = Number(accountInput.dataset.accountId || 0); const accountId = Number(accountInput.dataset.accountId || 0);
const debit = Number(tr.querySelectorAll("input")[1].value || 0); const numberInputs = tr.querySelectorAll("input[type='number']");
const credit = Number(tr.querySelectorAll("input")[2].value || 0); const debit = Number(numberInputs[0]?.value || 0);
const credit = Number(numberInputs[1]?.value || 0);
if (debit === 0 && credit === 0) return; if (debit === 0 && credit === 0) return;
lines.push({ account_id: accountId, debit, credit }); lines.push({ account_id: accountId, debit, credit });
d += debit; d += debit;

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,18 @@
// =============================================== // ===============================================
console.log("🔧 trial-balance.js ファイルが読み込まれました"); console.log("🔧 trial-balance.js ファイルが読み込まれました");
// 初期读入関数 // 防止重复初始化
let trialBalanceInitialized = false;
// 初期読込関数
function initTrialBalance() { function initTrialBalance() {
// 已经初始化过就直接返回
if (trialBalanceInitialized) {
console.log("⚠️ initTrialBalance() はすでに実行済みです");
return;
}
trialBalanceInitialized = true;
console.log("✓ initTrialBalance() 関数が実行されました"); console.log("✓ initTrialBalance() 関数が実行されました");
const API_URL = `/trial-balance`; const API_URL = `/trial-balance`;
@@ -69,12 +79,90 @@ function initTrialBalance() {
tbody.innerHTML = ""; tbody.innerHTML = "";
let totalOpening = 0; // =========================================
let totalDebit = 0; // 科目タイプ判定関数(日本企業損益計算書対応)
let totalCredit = 0; // =========================================
let totalClosing = 0; function getAccountType(accountCode) {
if (!accountCode) return "unknown";
const code = parseInt(accountCode);
// 営業収益(売上高)
if (code === 701) return "operating_revenue";
// 非営業収益(受取利息、雑収入)
if (code === 702 || code === 709) return "non_operating_revenue";
// 売上原価(仕入高)
if (code === 801) return "cogs";
// 営業費用仕入高以外の800-899、ただし520-599は除外
if (code >= 802 && code <= 899) return "operating_expense";
// 法人税等504- 損益計算に含めない
if (code === 504) return "corporate_tax";
// 消費税等505- 損益計算に含めない
if (code === 505) return "consumption_tax";
// その他は貸借科目
return "balance_sheet";
}
// =========================================
// 各タイプの合計集計(日本企業損益計算書対応)
// =========================================
let operatingRevenue = 0; // 701: 売上高(貸方)
let cogs = 0; // 801: 売上原価(借方)
let operatingExpenses = 0; // 802-820: 営業費用(借方)
let nonOperatingRevenue = 0; // 702, 709: 営業外収益(貸方)
let corporateTax = 0; // 504: 法人税等(貸方)
let consumptionTax = 0; // 505: 消費税(貸方)
// 税理士資料形式P/L科目の原始借方・貸方合計
let plTotalDebit = 0; // 全費用科目の借方合計801-820
let plTotalCredit = 0; // 全収益科目の貸方合計701-709
let bsDebit = 0; // 貸借科目合計(借方)
let bsCredit = 0; // 貸借科目合計(貸方)
data.accounts.forEach((row) => { data.accounts.forEach((row) => {
const code = row.account_code;
const debit = Number(row.debit ?? 0);
const credit = Number(row.credit ?? 0);
const type = getAccountType(code);
if (type === "operating_revenue") {
operatingRevenue += credit - debit;
plTotalCredit += credit;
} else if (type === "non_operating_revenue") {
nonOperatingRevenue += credit - debit;
plTotalCredit += credit;
} else if (type === "cogs") {
cogs += debit - credit;
plTotalDebit += debit;
} else if (type === "operating_expense") {
operatingExpenses += debit - credit;
plTotalDebit += debit;
} else if (type === "balance_sheet") {
bsDebit += debit;
bsCredit += credit;
}
});
// 日本企業損益計算書の標準フロー
// ※ 504(未払法人税等)、505(未払消費税等) は負債科目のため損益計算に含めない
const grossProfit = operatingRevenue - cogs; // 売上総利益 = 売上高 - 売上原価
const operatingProfit = grossProfit - operatingExpenses; // 営業利益 = 売上総利益 - 営業費用
const ordinaryProfit = operatingProfit + nonOperatingRevenue; // 経常利益 = 営業利益 + 営業外収益
// 801-820科目の合計を計算用の変量
let operatingExpense801_820Debit = 0;
let operatingExpense801_820Credit = 0;
// 税引前当期純利益 = 経常利益(特別損益の分類がないため)
const netProfit = ordinaryProfit; // 当期純利益 = 経常利益(法人税等は損益計算に含めない)
data.accounts.forEach((row) => {
const code = row.account_code;
const debit = Number(row.debit ?? 0); const debit = Number(row.debit ?? 0);
const credit = Number(row.credit ?? 0); const credit = Number(row.credit ?? 0);
const opening = Number(row.opening_balance ?? 0); const opening = Number(row.opening_balance ?? 0);
@@ -85,14 +173,6 @@ function initTrialBalance() {
// ========================= // =========================
const isTotalRow = !row.account_code; // null / "" / undefined 全覆盖 const isTotalRow = !row.account_code; // null / "" / undefined 全覆盖
// 明细行だけ合計に含める
if (!isTotalRow) {
totalOpening += opening;
totalDebit += debit;
totalCredit += credit;
totalClosing += closing;
}
const tr = document.createElement("tr"); const tr = document.createElement("tr");
if (isTotalRow) { if (isTotalRow) {
@@ -106,32 +186,32 @@ function initTrialBalance() {
const ratio = row.ratio ?? "0.00"; const ratio = row.ratio ?? "0.00";
// =========================
// 借方・貸方は全科目共通でそのまま表示
// =========================
const displayDebit = debit ? debit.toLocaleString() : "";
const displayCredit = credit ? credit.toLocaleString() : "";
// 期末残高のフォーマット(マイナス値も表示)
let displayClosing = "";
if (closing !== 0) {
displayClosing = closing.toLocaleString();
}
tr.innerHTML = ` tr.innerHTML = `
<td class="left">${row.account_code ?? ""}</td> <td class="left">${row.account_code ?? ""}</td>
<td class="left">${row.account_name ?? ""}</td> <td class="left">${row.account_name ?? ""}</td>
<td class="right">${opening ? opening.toLocaleString() : ""}</td> <td class="right">${opening ? opening.toLocaleString() : ""}</td>
<td class="right">${debit ? debit.toLocaleString() : ""}</td> <td class="right">${displayDebit}</td>
<td class="right">${credit ? credit.toLocaleString() : ""}</td> <td class="right">${displayCredit}</td>
<td class="right">${closing ? closing.toLocaleString() : ""}</td> <td class="right">${displayClosing}</td>
<td class="right">${ratio}</td> <td class="right">${ratio}</td>
`; `;
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
// ========================= // フッター合計は削除
// フッター合計
// =========================
document.getElementById("totalOpening").textContent =
totalOpening.toLocaleString();
document.getElementById("totalDebit").textContent =
totalDebit.toLocaleString();
document.getElementById("totalCredit").textContent =
totalCredit.toLocaleString();
document.getElementById("totalClosing").textContent =
totalClosing.toLocaleString();
document.getElementById("totalRatio").textContent =
data.totals?.ratio ?? "100.00";
// 加載中ステータスを非表示 // 加載中ステータスを非表示
const loadingEl = document.getElementById("loadingStatus"); const loadingEl = document.getElementById("loadingStatus");
@@ -166,9 +246,15 @@ function initTrialBalance() {
toDate.getMonth() + 1 toDate.getMonth() + 1
}${toDate.getDate()}`; }${toDate.getDate()}`;
document.getElementById("periodInfo").textContent = periodText; document.getElementById("periodInfo").textContent = periodText;
// thead内の期間情報も更新印刷用
const theadPeriodInfo = document.getElementById("theadPeriodInfo");
if (theadPeriodInfo) {
theadPeriodInfo.textContent = periodText;
}
} }
// 検索ボタンのイベント // 檢索按鈕的事件
document.getElementById("btnSearch").addEventListener("click", () => { document.getElementById("btnSearch").addEventListener("click", () => {
const dateFrom = document.getElementById("dateFrom").value; const dateFrom = document.getElementById("dateFrom").value;
const dateTo = document.getElementById("dateTo").value; const dateTo = document.getElementById("dateTo").value;
@@ -198,6 +284,31 @@ function initTrialBalance() {
loadTrialBalance(dateFrom, dateTo); loadTrialBalance(dateFrom, dateTo);
}); });
// 印刷ボタンのイベント(最新データを読み込んでから印刷)
document.getElementById("btnPrint").addEventListener("click", () => {
const dateFrom = document.getElementById("dateFrom").value;
const dateTo = document.getElementById("dateTo").value;
if (!dateFrom || !dateTo) {
alert("開始年月日と終了年月日を入力してください");
return;
}
if (dateFrom > dateTo) {
alert("開始年月日は終了年月日より前の日付を指定してください");
return;
}
// 最新データを読込んでから印刷
loadTrialBalance(dateFrom, dateTo);
// データ読込完了を待って印刷開始(少し遅延させる)
setTimeout(() => {
setupPrintDate(); // 打印前更新日期
window.print();
}, 800);
});
// 初期読み込み5/31決算に対応 // 初期読み込み5/31決算に対応
try { try {
const defaultPeriod = getDefaultPeriod(); const defaultPeriod = getDefaultPeriod();
@@ -219,3 +330,38 @@ if (document.readyState === "loading") {
// ページがすでに読み込まれている場合、すぐに実行 // ページがすでに読み込まれている場合、すぐに実行
setTimeout(initTrialBalance, 100); setTimeout(initTrialBalance, 100);
} }
// 打印日期和页码设置
// ===============================================
function setupPrintDate() {
const printDateEl = document.getElementById("printDate");
if (!printDateEl) return;
// 设置打印日期
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, "0");
const date = String(today.getDate()).padStart(2, "0");
const dateStr = `${year}${month.replace(/^0/, "")}${date.replace(
/^0/,
"",
)}`;
printDateEl.textContent = `印刷日:${dateStr}`;
// thead内の印刷日も更新
const theadPrintDate = document.getElementById("theadPrintDate");
if (theadPrintDate) {
theadPrintDate.textContent = `印刷日:${dateStr}`;
console.log("✓ 印刷日を更新しました:", theadPrintDate.textContent);
} else {
console.warn("⚠️ theadPrintDate要素が見つかりません");
}
}
// 页面加载时设置一次(仅一次)
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setupPrintDate, { once: true });
} else {
setupPrintDate();
}

View File

@@ -83,6 +83,10 @@
td.right { td.right {
text-align: right; text-align: right;
} }
td.center,
th.center {
text-align: center;
}
tr.total-row td { tr.total-row td {
background-color: #d8d8d8 !important; background-color: #d8d8d8 !important;
font-weight: bold; font-weight: bold;
@@ -91,10 +95,171 @@
tr.indent-row td:nth-child(2) { tr.indent-row td:nth-child(2) {
padding-left: 40px !important; padding-left: 40px !important;
} }
tfoot td { .print-button {
font-weight: bold; background: #ff9800;
background: #e0e0e0; color: white;
border-top: 2px solid #333; border: none;
padding: 8px 16px;
margin-left: 10px;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
}
.print-button:hover {
background: #e68900;
}
/* 画面表示時thead内の印刷用ヘッダー行を非表示 */
tr.print-header-title,
tr.print-header-period,
tr.print-header-meta {
display: none;
}
.print-header-cell {
border: none !important;
background: none !important;
background-color: white !important;
padding: 2px 5px !important;
}
@media print {
* {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
color-adjust: exact !important;
}
body {
margin: 0;
padding: 0;
font-size: 11px;
}
/* 画面用の要素を印刷時に隠す */
#printDate,
h2,
.period-info,
.back-button,
.search-form,
.print-button,
#loadingStatus {
display: none !important;
}
/* thead内の印刷用ヘッダー行を表示 */
tr.print-header-title,
tr.print-header-period,
tr.print-header-meta {
display: table-row !important;
}
.print-header-cell {
border: none !important;
background: none !important;
background-color: white !important;
padding: 3px 5px !important;
}
/* 印刷時の単位・印刷日のスタイリング */
tr.print-header-meta th {
border: none !important;
background: none !important;
padding: 2px 5px !important;
text-align: right !important;
}
tr.print-header-meta th:last-child {
text-align: right !important;
}
tr.print-header-meta div {
text-align: right !important;
font-size: 10px !important;
font-weight: normal !important;
display: block !important;
width: 100% !important;
}
#theadPrintDate {
text-align: right !important;
align-items: center !important;
flex-wrap: wrap !important;
gap: 20px !important;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 8px;
}
thead {
display: table-header-group;
page-break-after: avoid;
}
tfoot {
display: none !important;
}
th {
background: #e8e8e8 !important;
font-weight: bold;
padding: 5px 6px;
border: 1px solid #666;
page-break-inside: avoid;
font-size: 11px;
}
td {
border: 1px solid #666;
padding: 4px 5px;
font-size: 10px;
white-space: nowrap;
}
tr.total-row td {
background-color: #d8d8d8 !important;
font-weight: bold;
border-top: 2px solid #333;
page-break-inside: avoid;
}
tbody tr {
page-break-inside: avoid;
}
tr.indent-row td:nth-child(2) {
padding-left: 20px !important;
}
td.left {
text-align: left;
white-space: normal;
}
td.right {
text-align: right;
white-space: nowrap;
}
td.center,
th.center {
text-align: center;
}
}
@page {
size: A4 portrait;
margin-top: 10mm;
margin-bottom: 20mm;
margin-left: 10mm;
margin-right: 10mm;
@bottom-center {
content: "第 " counter(page) " 页";
font-size: 10px;
}
} }
</style> </style>
</head> </head>
@@ -115,6 +280,7 @@
> >
← 首ページに戻る ← 首ページに戻る
</button> </button>
<h2>残高試算表(貸借・損益)</h2> <h2>残高試算表(貸借・損益)</h2>
<div <div
@@ -133,18 +299,68 @@
<button id="btnSearch">検索</button> <button id="btnSearch">検索</button>
<button id="btnFullYear" class="secondary">全年度表示</button> <button id="btnFullYear" class="secondary">全年度表示</button>
<button id="btnPrint" class="print-button">印刷</button>
</div> </div>
<div class="period-info"> <div class="period-info">
<span id="periodInfo">令和7年 1月 1日 令和7年 12月31日</span> <span id="periodInfo">令和7年 1月 1日 令和7年 12月31日</span>
<span style="margin-left: 20px">単位: 円</span> <span style="margin-left: 20px; float: right">単位: 円</span>
</div> </div>
<table id="trialBalanceTable"> <table id="trialBalanceTable">
<thead> <thead>
<tr class="print-header-title">
<th
colspan="7"
class="print-header-cell"
style="text-align: center; font-size: 14px; font-weight: bold"
>
残高試算表(貸借・損益)
</th>
</tr>
<tr class="print-header-period">
<th
colspan="7"
class="print-header-cell"
style="text-align: center; font-size: 11px; font-weight: normal"
id="theadPeriodInfo"
></th>
</tr>
<tr class="print-header-meta">
<th colspan="5" class="print-header-cell"></th>
<th
colspan="2"
class="print-header-cell"
style="text-align: right !important; padding: 2px 5px"
>
<div
style="
text-align: right !important;
font-size: 10px;
line-height: 1.2;
display: block;
width: 100%;
"
id="theadPrintDate"
>
印刷日2026年2月25日
</div>
<div
style="
text-align: right !important;
font-size: 10px;
line-height: 1.2;
display: block;
width: 100%;
"
>
単位:円
</div>
</th>
</tr>
<tr> <tr>
<th class="left">科目コード</th> <th class="left">科目コード</th>
<th class="left">科目名称</th> <th class="center">科目名称</th>
<th>期首残高</th> <th>期首残高</th>
<th>借方発生額</th> <th>借方発生額</th>
<th>貸方発生額</th> <th>貸方発生額</th>
@@ -155,16 +371,6 @@
<tbody> <tbody>
<!-- JS 动态插入 --> <!-- JS 动态插入 -->
</tbody> </tbody>
<tfoot>
<tr>
<td colspan="2" class="left">合計</td>
<td id="totalOpening">0</td>
<td id="totalDebit">0</td>
<td id="totalCredit">0</td>
<td id="totalClosing">0</td>
<td id="totalRatio">100.00</td>
</tr>
</tfoot>
</table> </table>
<script src="js/trial-balance.js"></script> <script src="js/trial-balance.js"></script>

57
list_all_accounts.py Normal file
View File

@@ -0,0 +1,57 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
print("=" * 80)
print("【所有账户和小口現金/未払金 相关账户查询】")
print("=" * 80)
with get_connection() as conn:
with conn.cursor() as cur:
# 查找包含"小口"或"現金"的科目
print("\n【小口現金 相关账户】")
cur.execute("""
SELECT account_id, account_code, account_name, account_type
FROM accounts
WHERE account_name LIKE '%小口%' OR account_name LIKE '%現金%'
ORDER BY account_id
""")
cash_accounts = cur.fetchall()
if cash_accounts:
for acc in cash_accounts:
print(f" • ID {acc['account_id']}: {acc['account_code']} {acc['account_name']} ({acc['account_type']})")
else:
print(" (该类账户未找到)")
# 查找包含"未払"或"应"的科目
print("\n【未払金/应付类账户】")
cur.execute("""
SELECT account_id, account_code, account_name, account_type
FROM accounts
WHERE account_name LIKE '%未払%' OR account_name LIKE '%%' OR account_code = '502'
ORDER BY account_id
""")
payable_accounts = cur.fetchall()
if payable_accounts:
for acc in payable_accounts:
print(f" • ID {acc['account_id']}: {acc['account_code']} {acc['account_name']} ({acc['account_type']})")
else:
print(" (该类账户未找到)")
# 所有的账户列表
print("\n【所有账户列表】")
cur.execute("""
SELECT account_id, account_code, account_name
FROM accounts
ORDER BY account_id
LIMIT 30
""")
all_accounts = cur.fetchall()
for acc in all_accounts:
print(f" • ID {acc['account_id']}: {acc['account_code']} {acc['account_name']}")
print("\n" + "=" * 80)

107
query_accounts.py Normal file
View File

@@ -0,0 +1,107 @@
import psycopg2
import sys
# 数据库连接
try:
conn = psycopg2.connect(
host="192.168.0.61",
port=55432,
database="njts_acct",
user="njts_app",
password="njts_app2025"
)
except Exception as e:
print(f"数据库连接失败: {e}")
sys.exit(1)
cur = conn.cursor()
# 查询所有科目
try:
cur.execute("""
SELECT
account_id,
account_code,
account_name,
account_type
FROM accounts
ORDER BY CAST(account_code AS INTEGER)
""")
results = cur.fetchall()
except Exception as e:
print(f"查询失败: {e}")
sys.exit(1)
print("=" * 120)
print("全科目リスト - 科目代码范围分析")
print("=" * 120)
ranges = [
("100-199 (资产-现金预金)", 100, 199),
("200-299 (资产-应收账款)", 200, 299),
("300-399 (资产-有价证券)", 300, 399),
("400-499 (资产-固定资产)", 400, 499),
("500-599 (负债)", 500, 599),
("600-699 (纯资产)", 600, 699),
("700-799 (收益/收入)", 700, 799),
("800-899 (费用)", 800, 899),
("900-999 (其他)", 900, 999),
]
for label, min_code, max_code in ranges:
print(f"\n{label}")
filtered = [(id, code, name, typ) for id, code, name, typ in results
if min_code <= int(code) <= max_code]
if filtered:
for account_id, account_code, account_name, account_type in filtered:
print(f" {account_code:>3s} - {account_name:35s} (ID:{account_id:>3d}, Type:{account_type:10s})")
else:
print(" (无科目)")
# 打印按 account_type 的分类
print("\n" + "=" * 120)
print("按 account_type 分类")
print("=" * 120)
cur.execute("""
SELECT DISTINCT account_type
FROM accounts
ORDER BY account_type
""")
types = cur.fetchall()
for (acc_type,) in types:
print(f"\n{acc_type}")
filtered = [(id, code, name) for id, code, name, typ in results if typ == acc_type]
for account_id, account_code, account_name in filtered:
code_int = int(account_code)
if 700 <= code_int < 800:
category = "收益"
elif 800 <= code_int < 900:
category = "费用"
else:
category = "其他"
print(f" {account_code:>3s} - {account_name:35s} (ID:{account_id:>3d})")
print("\n" + "=" * 120)
print("科目代码范围统计")
print("=" * 120)
code_ranges = {
"700-799": (700, 799),
"800-899": (800, 899),
"505": (505, 505),
}
for label, (min_code, max_code) in code_ranges.items():
count = len([1 for _, code, _, _ in results if min_code <= int(code) <= max_code])
print(f"{label}: {count} 个科目")
cur.close()
conn.close()
print("\n✓ 查询完成")

112
replacement_analysis.py Normal file
View File

@@ -0,0 +1,112 @@
import sys
sys.path.insert(0, '/app')
from app.core.database import get_connection
from datetime import datetime
print("=" * 80)
print("【account_id 53小口現金 → 20未払金の置換前分析】")
print("=" * 80)
with get_connection() as conn:
with conn.cursor() as cur:
# 1. 影響する仕訳の件数
print("\n【1】影響する仕訳行の件数")
cur.execute("""
SELECT COUNT(*) as cnt
FROM journal_lines
WHERE account_id = 53
""")
result = cur.fetchone()
print(f" • account_id 53 を含む仕訳行: {result['cnt']}")
if result['cnt'] > 0:
# 2. 金額の影響
print("\n【2】金額の影響")
cur.execute("""
SELECT
SUM(CASE WHEN debit > 0 THEN debit ELSE 0 END) as total_debit,
SUM(CASE WHEN credit > 0 THEN credit ELSE 0 END) as total_credit
FROM journal_lines
WHERE account_id = 53
""")
result_amount = cur.fetchone()
print(f" • 借方合計: {result_amount['total_debit']}")
print(f" • 貸方合計: {result_amount['total_credit']}")
# 3. 影響する仕訳の詳細
print("\n【3】影響する仕訳の詳細最初の 20 件)")
cur.execute("""
SELECT
je.journal_entry_id,
je.entry_date,
je.description,
a.account_code,
a.account_name,
jl.debit,
jl.credit
FROM journal_lines jl
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
JOIN accounts a ON jl.account_id = a.account_id
WHERE jl.account_id = 53
ORDER BY je.entry_date DESC
LIMIT 20
""")
for i, row in enumerate(cur.fetchall(), 1):
side = '借方' if row['debit'] > 0 else '貸方'
amount = row['debit'] if row['debit'] > 0 else row['credit']
print(f" {i}. {row['entry_date']} - {row['description'][:40]}...")
print(f" {row['account_code']} {row['account_name']}: {side} {amount}")
# 4. 会計的影響の分析
print("\n【4】会計的な影響警告")
print(" 【問題1】資産 vs 負債の分類変更")
print(" • 小口現金105= 流動資産")
print(" • 未払金502= 流動負債")
print(" → 貸借対照表の構成が大きく変わります")
print("\n 【問題2】簿記ルール違反の可能性")
cur.execute("""
SELECT
COUNT(DISTINCT je.journal_entry_id) as journal_count,
MAX(je.entry_date) as latest_date
FROM journal_lines jl
JOIN journal_entries je ON jl.journal_entry_id = je.journal_entry_id
WHERE jl.account_id = 53
""")
result_check = cur.fetchone()
print(f" • 影響する仕訳: {result_check['journal_count']}")
print(f" • 最新の仕訳: {result_check['latest_date']}")
print(" → これらの仕訳の相手科目との整合性が乱れる可能性")
print("\n 【問題3】監査証跡の喪失")
print(" • 元の科目情報が失われます")
print(" → 修正履歴は保存されていません(一度背き不可)")
else:
print(" • account_id 53 の仕訳行は存在しません(置換は不要)")
print("\n" + "=" * 80)
print("【SQL 置換コマンド】")
print("=" * 80)
print("""
-- バックアップ推奨:先に元のデータをダンプしてください
-- pg_dump accounting_db > backup_before_replacement.sql
-- 置換実行SQL自己責任で実行してください
BEGIN; -- トランザクション開始
UPDATE journal_lines
SET account_id = 20
WHERE account_id = 53;
-- 確認用SQL実行前に確認してください
-- SELECT COUNT(*) FROM journal_lines WHERE account_id = 20;
COMMIT; -- コミット(このコマンドで確定)
-- ROLLBACK; -- ロールバック(中止したい場合)
""")
print("=" * 80)

57
test_api_fix.py Normal file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Test if the trial-balance API fix is applied"""
import requests
import json
url = 'http://127.0.0.1:18080/trial-balance'
params = {
'fiscal_year': 2025,
'date_from': '2025-06-01',
'date_to': '2025-12-31'
}
print("🔍 Testing API...")
print(f"URL: {url}")
print(f"Params: {params}")
print()
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
# Find the target row
target = [row for row in data if row.get('account_name') == '繰越利益剰余金合計']
if target:
row = target[0]
print("✓ Found: 繰越利益剰余金合計")
print(f" debit: {row.get('debit')}")
print(f" credit: {row.get('credit')}")
print()
print("Expected (after fix):")
print(" debit: 0")
print(" credit: 7967855")
print()
debit_correct = row.get('debit') == '0'
credit_correct = row.get('credit') == '7967855'
if debit_correct and credit_correct:
print("✓✓✓ SUCCESS! API is now returning CORRECT values.")
print("The fix has been successfully applied!")
else:
print("❌ Values still incorrect")
if not debit_correct:
print(f" - debit is {row.get('debit')}, expected 0")
if not credit_correct:
print(f" - credit is {row.get('credit')}, expected 7967855")
else:
print("❌ Cannot find 繰越利益剰余金合計 row")
print("Last 5rows:")
for row in data[-5:]:
print(f" {row.get('account_name')}: debit={row.get('debit')}, credit={row.get('credit')}")
except Exception as e:
print(f"❌ Error: {e}")
print("\nMake sure the backend is running on http://127.0.0.1:18080")

76
test_api_fix_v2.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Test if the trial-balance API fix is applied"""
import requests
import json
url = 'http://127.0.0.1:18080/trial-balance'
params = {
'fiscal_year': 2025,
'date_from': '2025-06-01',
'date_to': '2025-12-31'
}
print("🔍 Testing API...")
print(f"URL: {url}")
print(f"Params: {params}")
print()
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
# Check if data is a dict or list
print(f"Response type: {type(data)}")
if isinstance(data, dict):
print("Response is a dict, checking structure...")
print(json.dumps(data, indent=2, ensure_ascii=False)[:500])
# Check if it has a data key
if 'data' in data:
data = data['data']
else:
print("\nDict keys:", list(data.keys())[:5])
if isinstance(data, list):
print(f"Response is a list with {len(data)} items")
# Find the target row
target = [row for row in data if isinstance(row, dict) and row.get('account_name') == '繰越利益剰余金合計']
if target:
row = target[0]
print("\n✓ Found: 繰越利益剰余金合計")
print(f" debit: {row.get('debit')}")
print(f" credit: {row.get('credit')}")
print()
print("Expected (after fix):")
print(" debit: 0")
print(" credit: 7967855")
print()
debit_correct = row.get('debit') == '0'
credit_correct = row.get('credit') == '7967855'
if debit_correct and credit_correct:
print("✓✓✓ SUCCESS! API is now returning CORRECT values.")
print("The fix has been successfully applied!")
else:
print("❌ Values still incorrect")
if not debit_correct:
print(f" - debit is {row.get('debit')}, expected 0")
if not credit_correct:
print(f" - credit is {row.get('credit')}, expected 7967855")
else:
print("❌ Cannot find 繰越利益剰余金合計 row")
print("Last 5 rows:")
for row in data[-5:]:
if isinstance(row, dict):
print(f" {row.get('account_name')}: debit={row.get('debit')}, credit={row.get('credit')}")
except Exception as e:
print(f"❌ Error: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
print("\nMake sure the backend is running on http://127.0.0.1:18080")

BIN
verification_result.txt Normal file

Binary file not shown.

70
verify_final_changes.py Normal file
View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Verify the print modification is correct"""
import requests
print("🔍 Verifying print modifications...\n")
# Check HTML
print("📄 Checking HTML...")
html_response = requests.get("http://127.0.0.1:18080/trial-balance.html")
html = html_response.text
html_checks = [
("First page header preserved", '<h2>残高試算表(貸借・損益)</h2>', True),
("Period info preserved", 'id="periodInfo"', True),
("Print date div preserved", 'id="printDate"', True),
("Old printHeaderRow removed", 'id="printHeaderRow"', False),
("Original @page structure", '@page {', True),
]
html_pass = 0
for check_name, needle, should_exist in html_checks:
found = needle in html
status = "" if found == should_exist else ""
result = "found" if found else "not found"
expected = "should exist" if should_exist else "should NOT exist"
if found == should_exist:
html_pass += 1
print(f" {status} {check_name}: {result} ({expected})")
# Check JavaScript
print("\n📝 Checking JavaScript...")
js_response = requests.get("http://127.0.0.1:18080/js/trial-balance.js")
js = js_response.text
js_checks = [
("insertPageHeaders function", "function insertPageHeaders", True),
("removePageHeaders function", "function removePageHeaders", True),
("insertPageHeaders called in print", "insertPageHeaders(dateFrom, dateTo)", True),
("removePageHeaders called after print", "removePageHeaders()", True),
("Old setupPrintHeaderInfo removed", "function setupPrintHeaderInfo", False),
("Old formatPeriodInfo removed", "function formatPeriodInfo", False),
]
js_pass = 0
for check_name, needle, should_exist in js_checks:
found = needle in js
status = "" if found == should_exist else ""
result = "found" if found else "not found"
expected = "should exist" if should_exist else "should NOT exist"
if found == should_exist:
js_pass += 1
print(f" {status} {check_name}: {result} ({expected})")
# Summary
print(f"\n✅ HTML checks: {html_pass}/{len(html_checks)} passed")
print(f"✅ JS checks: {js_pass}/{len(js_checks)} passed")
if html_pass == len(html_checks) and js_pass == len(js_checks):
print("\n🎉 All modifications verified successfully!")
print("📌 Summary of changes:")
print(" - Reverted printHeaderRow from HTML")
print(" - Restored original @page structure")
print(" - Added insertPageHeaders() for dynamic page headers")
print(" - Added removePageHeaders() to clean up after printing")
print(" - First page remains unchanged")
print(" - Each printed page will show title, period, date, and unit")
else:
print("\n⚠️ Some checks failed. Please review the modifications.")

81
verify_pl.py Normal file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import requests
import json
# 試算表API取得
api_url = "http://127.0.0.1:18080/trial-balance"
params = {
"date_from": "2025-06-01",
"date_to": "2025-12-31"
}
response = requests.get(api_url, params=params, timeout=10)
if response.status_code != 200:
print("API Error:", response.text)
exit(1)
data = response.json()
# 損益計算科目を全て抽出
pl_items = {}
for row in data.get('accounts', []):
code = str(row.get('account_code', '')).strip()
name = row.get('account_name', '')
debit = float(row.get('debit', 0) or 0)
credit = float(row.get('credit', 0) or 0)
# 損益計算科目
if code in ('701', '702', '709', '801', '802', '803', '804', '805', '806', '807', '808', '809', '810', '811', '812', '813', '814', '815', '816', '817', '818', '819', '820', '504', '505'):
pl_items[code] = {
'name': name,
'debit': debit,
'credit': credit
}
# 全損益科目出力
print("=" * 80)
print("P/L Items Details")
print("=" * 80)
print(f"{'Code':<10} {'Name':<30} {'Debit':>15} {'Credit':>15}")
print("-" * 80)
total_operating_expenses = 0
for code in sorted(pl_items.keys()):
item = pl_items[code]
print(f"{code:<10} {item['name']:<30} {item['debit']:>15,.0f} {item['credit']:>15,.0f}")
if code in ('802', '803', '804', '805', '806', '807', '808', '809', '810', '811', '812', '813', '814', '815', '816', '817', '818', '819', '820'):
total_operating_expenses += item['debit'] - item['credit']
print("-" * 80)
# 計算
operating_revenue = pl_items.get('701', {}).get('credit', 0) - pl_items.get('701', {}).get('debit', 0)
cogs = pl_items.get('801', {}).get('debit', 0) - pl_items.get('801', {}).get('credit', 0)
non_op_702 = pl_items.get('702', {}).get('credit', 0) - pl_items.get('702', {}).get('debit', 0)
non_op_709 = pl_items.get('709', {}).get('credit', 0) - pl_items.get('709', {}).get('debit', 0)
non_operating_revenue = non_op_702 + non_op_709
corporate_tax = pl_items.get('504', {}).get('credit', 0) - pl_items.get('504', {}).get('debit', 0)
consumption_tax = pl_items.get('505', {}).get('credit', 0) - pl_items.get('505', {}).get('debit', 0)
gross_profit = operating_revenue - cogs
operating_profit = gross_profit - total_operating_expenses
ordinary_profit = operating_profit + non_operating_revenue
# 当期純利益(税引前)= 経常利益504,505は負債科目のため含めない
net_profit = ordinary_profit
print("\nCalculation Results:")
print(f"Operating Revenue (701): {operating_revenue:,.0f}")
print(f"COGS (801): {cogs:,.0f}")
print(f"Gross Profit: {gross_profit:,.0f}")
print(f"Operating Expenses: {total_operating_expenses:,.0f}")
print(f"Operating Profit: {operating_profit:,.0f}")
print(f"Non-op Revenue: {non_operating_revenue:,.0f}")
print(f"Ordinary Profit: {ordinary_profit:,.0f}")
print(f"NET PROFIT (Before Tax): {net_profit:,.0f}")
print(f"\nAccountant Data: 7193796")
print(f"Difference: {net_profit - 7193796:,.0f}")
print(f"(Note: Our amount is PRE-TAX. Accountant data may include tax effects.)")

43
verify_print_headers.py Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Verify the print header changes are in place"""
import requests
urls = [
("HTML", "http://127.0.0.1:18080/trial-balance.html"),
("JS", "http://127.0.0.1:18080/js/trial-balance.js"),
]
for name, url in urls:
try:
response = requests.get(url)
content = response.text
print(f"\n{name} loaded successfully")
# Define checks for each file type
if name == "HTML":
checks = [
("printHeaderRow", 'id="printHeaderRow"'),
("headerPeriodInfo", 'id="headerPeriodInfo"'),
("headerPrintDate", 'id="headerPrintDate"'),
("headerUnit", 'id="headerUnit"'),
]
else: # JS
checks = [
("setupPrintHeaderInfo function", "function setupPrintHeaderInfo"),
("formatPeriodInfo function", "function formatPeriodInfo"),
]
for check_name, needle in checks:
if needle in content:
print(f"{check_name} - Found")
else:
print(f"{check_name} - NOT Found")
# Debug: show file size
if len(content) < 500:
print(f" (File content: {content[:200]}...)")
except Exception as e:
print(f"✗ Error fetching {name}: {e}")
print("\n✓✓✓ All modifications verified!")