修正
This commit is contained in:
@@ -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
|
||||||
@@ -77,11 +58,9 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
""", (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,
|
||||||
@@ -100,7 +79,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
|
|
||||||
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):
|
||||||
|
"""個別科目の行を返す(存在しない場合はNone)"""
|
||||||
|
if code not in account_data:
|
||||||
|
return None
|
||||||
|
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):
|
||||||
inserted_totals = set()
|
"""合計行を生成"""
|
||||||
|
existing = [c for c in codes if c in account_data]
|
||||||
|
opening = sum(account_data[c]["opening_balance"] for c in existing) if existing else D(0)
|
||||||
|
debit = sum(account_data[c]["debit"] for c in existing) if existing else D(0)
|
||||||
|
credit = sum(account_data[c]["credit"] for c in existing) if existing else D(0)
|
||||||
|
closing = sum(account_data[c]["closing_balance"] for c in existing) if existing else D(0)
|
||||||
|
return {
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": name,
|
||||||
|
"account_type": "",
|
||||||
|
"opening_balance": str(opening),
|
||||||
|
"debit": str(debit),
|
||||||
|
"credit": str(credit),
|
||||||
|
"closing_balance": str(closing),
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": is_total,
|
||||||
|
"indent": indent,
|
||||||
|
}
|
||||||
|
|
||||||
# 大区分の合計を保存(資産・負債・純資産の総合計)
|
def custom_row(name, opening, debit, credit, closing,
|
||||||
grand_total_closing = D(0)
|
is_total=False, indent=False, account_type="", is_pl=False):
|
||||||
|
"""カスタム行を生成"""
|
||||||
|
row = {
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": name,
|
||||||
|
"account_type": account_type,
|
||||||
|
"opening_balance": str(opening),
|
||||||
|
"debit": str(debit),
|
||||||
|
"credit": str(credit),
|
||||||
|
"closing_balance": str(closing),
|
||||||
|
"ratio": None,
|
||||||
|
"is_total": is_total,
|
||||||
|
"indent": indent,
|
||||||
|
}
|
||||||
|
if is_pl:
|
||||||
|
row["is_pl"] = True
|
||||||
|
return row
|
||||||
|
|
||||||
# 個別科目を追加
|
# =============================================
|
||||||
for code in sorted(account_data.keys()):
|
# 結果リスト構築(試算表.pdf の順序に完全準拠)
|
||||||
data = account_data[code]
|
# =============================================
|
||||||
result_accounts.append({
|
result_accounts: List[Dict[str, Any]] = []
|
||||||
"account_code": data["account_code"],
|
added_codes = set() # 追加済みの科目コードを追跡
|
||||||
"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
|
|
||||||
})
|
|
||||||
|
|
||||||
# 大区分合計用に総計を積算
|
def add(code):
|
||||||
grand_total_closing += data["closing_balance"]
|
"""個別科目を追加"""
|
||||||
|
r = acct_row(code)
|
||||||
|
if r:
|
||||||
|
result_accounts.append(r)
|
||||||
|
added_codes.add(code)
|
||||||
|
|
||||||
# 利益剰余金(602)の後に詳細を挿入
|
def add_total(name, codes, **kwargs):
|
||||||
if code == "602":
|
"""合計行を追加"""
|
||||||
# 繰越利益剰余金 = 602の期首残高
|
result_accounts.append(total_row(name, codes, **kwargs))
|
||||||
retained_earnings_opening = data["opening_balance"]
|
|
||||||
|
|
||||||
# 当期純損益金額を計算(収益 - 費用)
|
# ========== 資産の部 ==========
|
||||||
# 収益(7xx)の合計
|
|
||||||
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"]
|
|
||||||
|
|
||||||
# 費用(8xx)の合計
|
# 現金・預金
|
||||||
expense_total = D(0)
|
cash_codes = ["101", "102", "103", "104", "105"]
|
||||||
for acc_code, acc_data in account_data.items():
|
for c in cash_codes:
|
||||||
if acc_code.startswith("8"):
|
add(c)
|
||||||
# 費用は借方が増加、貸方が減少
|
add_total("現金・預金合計", cash_codes)
|
||||||
expense_total += acc_data["debit"] - acc_data["credit"]
|
|
||||||
|
|
||||||
# 当期純損益金額 = 収益 - 費用
|
# 売上債権
|
||||||
net_income = revenue_total - expense_total
|
receivable_codes = ["201", "202"]
|
||||||
|
for c in receivable_codes:
|
||||||
|
add(c)
|
||||||
|
add_total("売上債権合計", receivable_codes)
|
||||||
|
|
||||||
# 繰越利益剰余金合計 = 期首繰越利益 + 当期純損益
|
# 他流動資産
|
||||||
retained_earnings_total = retained_earnings_opening + net_income
|
other_current_codes = ["203", "204", "205", "206", "207", "208"]
|
||||||
|
for c in other_current_codes:
|
||||||
|
add(c)
|
||||||
|
add_total("他流動資産合計", other_current_codes)
|
||||||
|
|
||||||
# 繰越利益の行を追加
|
# 有価証券
|
||||||
result_accounts.append({
|
securities_codes = ["301"]
|
||||||
"account_code": "",
|
for c in securities_codes:
|
||||||
"account_name": "繰越利益",
|
add(c)
|
||||||
"account_type": "equity",
|
add_total("有価証券合計", securities_codes)
|
||||||
"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({
|
current_asset_codes = cash_codes + receivable_codes + other_current_codes + securities_codes
|
||||||
"account_code": "",
|
add_total("流動資産合計", current_asset_codes)
|
||||||
"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({
|
tangible_codes = ["401", "402", "403", "404", "405", "406", "407"]
|
||||||
"account_code": "",
|
for c in tangible_codes:
|
||||||
"account_name": "繰越利益剰余金合計",
|
add(c)
|
||||||
"account_type": "equity",
|
add_total("有形固定資産合計", tangible_codes)
|
||||||
"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({
|
intangible_codes = ["408"]
|
||||||
"account_code": "",
|
for c in intangible_codes:
|
||||||
"account_name": "その他利益剰余金合計",
|
add(c)
|
||||||
"account_type": "equity",
|
add_total("無形固定資産合計", intangible_codes)
|
||||||
"opening_balance": "0",
|
|
||||||
"debit": "0",
|
|
||||||
"credit": "0",
|
|
||||||
"closing_balance": "0",
|
|
||||||
"ratio": None,
|
|
||||||
"is_total": True,
|
|
||||||
"indent": True
|
|
||||||
})
|
|
||||||
|
|
||||||
# 利益剰余金合計の行を追加
|
# 投資その他の資産
|
||||||
result_accounts.append({
|
investment_codes = ["410"]
|
||||||
"account_code": "",
|
for c in investment_codes:
|
||||||
"account_name": "利益剰余金合計",
|
add(c)
|
||||||
"account_type": "equity",
|
add_total("投資その他の資産合計", investment_codes)
|
||||||
"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:
|
fixed_asset_codes = tangible_codes + intangible_codes + investment_codes
|
||||||
# このグループにまだ合計行を挿入していない
|
add_total("固定資産合計", fixed_asset_codes)
|
||||||
if group_name in inserted_totals:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 現在の科目がこのグループに含まれる
|
# 資産合計
|
||||||
if code not in group_codes:
|
all_asset_codes = current_asset_codes + fixed_asset_codes
|
||||||
continue
|
add_total("資産合計", all_asset_codes)
|
||||||
|
|
||||||
# このグループの全科目を取得
|
# ========== 負債の部 ==========
|
||||||
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)
|
trade_payable_codes = ["501"]
|
||||||
|
for c in trade_payable_codes:
|
||||||
|
add(c)
|
||||||
|
add_total("仕入債務合計", trade_payable_codes)
|
||||||
|
|
||||||
# 現在の科目がグループの最後の科目の場合
|
# 流動負債その他(502-508)
|
||||||
if code == last_code_in_group:
|
current_liability_other = ["502", "503", "504", "505", "506", "507", "508"]
|
||||||
# グループ合計を計算
|
for c in current_liability_other:
|
||||||
group_opening = sum(account_data[gc]["opening_balance"] for gc in group_codes_in_data)
|
add(c)
|
||||||
group_debit = sum(account_data[gc]["debit"] for gc in group_codes_in_data)
|
current_liability_codes = trade_payable_codes + current_liability_other
|
||||||
group_credit = sum(account_data[gc]["credit"] for gc in group_codes_in_data)
|
add_total("流動負債合計", current_liability_codes)
|
||||||
group_closing = sum(account_data[gc]["closing_balance"] for gc in group_codes_in_data)
|
|
||||||
|
|
||||||
result_accounts.append({
|
# 固定負債
|
||||||
"account_code": "",
|
fixed_liability_codes = ["509"]
|
||||||
"account_name": group_name,
|
for c in fixed_liability_codes:
|
||||||
"account_type": "",
|
add(c)
|
||||||
"opening_balance": str(group_opening),
|
add_total("固定負債合計", fixed_liability_codes)
|
||||||
"debit": str(group_debit),
|
|
||||||
"credit": str(group_credit),
|
|
||||||
"closing_balance": str(group_closing),
|
|
||||||
"ratio": None, # 後で計算
|
|
||||||
"is_total": True
|
|
||||||
})
|
|
||||||
|
|
||||||
inserted_totals.add(group_name)
|
# 負債合計
|
||||||
|
all_liability_codes = current_liability_codes + fixed_liability_codes
|
||||||
|
add_total("負債合計", all_liability_codes)
|
||||||
|
|
||||||
# 構成比を計算(総資産に対する割合)
|
# ========== 510/511: 独立科目(負債と資本の間) ==========
|
||||||
for acc in result_accounts:
|
add("510")
|
||||||
closing = D(acc["closing_balance"])
|
add("511")
|
||||||
if grand_total_closing != 0:
|
|
||||||
ratio = (closing / grand_total_closing) * 100
|
# ========== 資本の部 ==========
|
||||||
|
|
||||||
|
# 資本金
|
||||||
|
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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
113
backend/check_search_results.py
Normal file
113
backend/check_search_results.py
Normal 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
107
check_account_19_chain.py
Normal 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
69
check_accounts_105_502.py
Normal 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)
|
||||||
50
check_all_entries_in_period.py
Normal file
50
check_all_entries_in_period.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/app')
|
||||||
|
|
||||||
|
from app.core.database import get_connection
|
||||||
|
|
||||||
|
print("=" * 80)
|
||||||
|
print("【期間内(2025-06-01~06-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)
|
||||||
71
check_all_revision_chains.py
Normal file
71
check_all_revision_chains.py
Normal 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
69
check_api_fix.py
Normal 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
112
check_api_query.py
Normal 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)
|
||||||
53
check_chain_140_accounts.py
Normal file
53
check_chain_140_accounts.py
Normal 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
75
check_chain_140_detail.py
Normal 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-01~06-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
85
check_db_records.py
Normal 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
48
check_revisions.py
Normal 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
131
check_search_results.py
Normal 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)
|
||||||
@@ -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
BIN
docs/File.PDF
Normal file
Binary file not shown.
BIN
docs/File0001.PDF
Normal file
BIN
docs/File0001.PDF
Normal file
Binary file not shown.
BIN
docs/試算表.pdf
Normal file
BIN
docs/試算表.pdf
Normal file
Binary file not shown.
@@ -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
@@ -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();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
57
list_all_accounts.py
Normal 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
107
query_accounts.py
Normal 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
112
replacement_analysis.py
Normal 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
57
test_api_fix.py
Normal 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
76
test_api_fix_v2.py
Normal 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
BIN
verification_result.txt
Normal file
Binary file not shown.
70
verify_final_changes.py
Normal file
70
verify_final_changes.py
Normal 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
81
verify_pl.py
Normal 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
43
verify_print_headers.py
Normal 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!")
|
||||||
Reference in New Issue
Block a user