修正
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException, Query
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from app.core.database import get_connection
|
from app.core.database import get_connection
|
||||||
|
|
||||||
@@ -10,6 +10,25 @@ router = APIRouter(
|
|||||||
class LockRequest(BaseModel):
|
class LockRequest(BaseModel):
|
||||||
fiscal_year: int
|
fiscal_year: int
|
||||||
|
|
||||||
|
@router.get("/lock-status", summary="期首残高確定状態確認")
|
||||||
|
def get_lock_status(fiscal_year: int = Query(..., description="会計年度")):
|
||||||
|
try:
|
||||||
|
with get_connection() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT is_locked
|
||||||
|
FROM fiscal_year_locks
|
||||||
|
WHERE fiscal_year = %s
|
||||||
|
""", (fiscal_year,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
return {"is_locked": row[0]}
|
||||||
|
else:
|
||||||
|
return {"is_locked": False}
|
||||||
|
except Exception:
|
||||||
|
# テーブルが存在しない場合は未ロック扱い
|
||||||
|
return {"is_locked": False}
|
||||||
|
|
||||||
@router.post("/lock", summary="期首残高年度確定")
|
@router.post("/lock", summary="期首残高年度確定")
|
||||||
def lock_fiscal_year(req: LockRequest):
|
def lock_fiscal_year(req: LockRequest):
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,33 @@ 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 = [
|
||||||
|
# 資産の部
|
||||||
|
("現金・預金合計", ["101", "102", "103", "104"]),
|
||||||
|
("流動資産合計", ["101", "102", "103", "104", "201", "202", "203", "204", "205"]),
|
||||||
|
("有価証券合計", ["301"]),
|
||||||
|
("有形固定資産合計", ["401", "402", "403", "404", "405", "406", "407"]),
|
||||||
|
("無形固定資産合計", ["408"]),
|
||||||
|
("投資その他の資産合計", ["301", "410"]),
|
||||||
|
("固定資産合計", ["301", "401", "402", "403", "404", "405", "406", "407", "408", "410"]),
|
||||||
|
("資産合計", ["101", "102", "103", "104", "201", "202", "203", "204", "205", "301", "401", "402", "403", "404", "405", "406", "407", "408", "410"]),
|
||||||
|
# 負債の部
|
||||||
|
("仕入債務合計", ["501"]),
|
||||||
|
("流動負債合計", ["501", "502", "503", "504", "505", "506", "507", "508"]),
|
||||||
|
("固定負債合計", ["509"]),
|
||||||
|
("負債合計", ["501", "502", "503", "504", "505", "506", "507", "508", "509"]),
|
||||||
|
# 資本の部
|
||||||
|
("資本金合計", ["601"]),
|
||||||
|
("利益剰余金合計", ["602"]),
|
||||||
|
("純資産合計", ["601", "602"])
|
||||||
|
]
|
||||||
|
|
||||||
def fetch_trial_balance(date_from: str, date_to: str):
|
def fetch_trial_balance(date_from: str, date_to: str):
|
||||||
|
|
||||||
with get_connection() as conn, conn.cursor() as cur:
|
with get_connection() as conn, conn.cursor() as cur:
|
||||||
|
|
||||||
|
# 勘定科目一覧
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT account_id, account_code, account_name, account_type
|
SELECT account_id, account_code, account_name, account_type
|
||||||
FROM accounts
|
FROM accounts
|
||||||
@@ -18,37 +41,50 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
""")
|
""")
|
||||||
accounts = cur.fetchall()
|
accounts = cur.fetchall()
|
||||||
|
|
||||||
result_accounts: List[Dict[str, Any]] = []
|
# 科目データを格納
|
||||||
|
account_data: Dict[str, Dict[str, Any]] = {}
|
||||||
total_opening = Decimal("0")
|
|
||||||
total_debit = Decimal("0")
|
|
||||||
total_credit = Decimal("0")
|
|
||||||
total_closing = Decimal("0")
|
|
||||||
|
|
||||||
for acc in accounts:
|
for acc in accounts:
|
||||||
aid = acc["account_id"]
|
aid = acc["account_id"]
|
||||||
|
code = acc["account_code"]
|
||||||
|
|
||||||
# 期首
|
# 期首残高: opening_balances テーブルの残高 + date_from より前の仕訳
|
||||||
|
# まず opening_balances テーブルから取得
|
||||||
|
cur.execute("""
|
||||||
|
SELECT COALESCE(opening_debit, 0) - COALESCE(opening_credit, 0) AS ob
|
||||||
|
FROM opening_balances
|
||||||
|
WHERE account_id = %s
|
||||||
|
AND fiscal_year = EXTRACT(YEAR FROM %s::date)
|
||||||
|
""", (aid, date_from))
|
||||||
|
ob_row = cur.fetchone()
|
||||||
|
opening_balance_from_table = D(ob_row["ob"]) if ob_row else D(0)
|
||||||
|
|
||||||
|
# date_from より前の仕訳による増減
|
||||||
cur.execute("""
|
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
|
||||||
JOIN journal_entries j ON j.journal_id = l.journal_id
|
JOIN journal_entries j
|
||||||
AND j.is_deleted = false
|
ON j.journal_entry_id = l.journal_entry_id
|
||||||
WHERE l.account_id = %s
|
WHERE l.account_id = %s
|
||||||
AND j.journal_date < %s
|
AND j.entry_date < %s
|
||||||
|
AND j.is_deleted = false
|
||||||
""", (aid, date_from))
|
""", (aid, date_from))
|
||||||
opening = D(cur.fetchone()["opening"])
|
opening_from_journals = D(cur.fetchone()["opening"])
|
||||||
|
|
||||||
# 当期
|
# 合計
|
||||||
|
opening = opening_balance_from_table + opening_from_journals
|
||||||
|
|
||||||
|
# 当期増減(date_from ~ date_to)
|
||||||
cur.execute("""
|
cur.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(SUM(l.debit), 0) AS debit,
|
COALESCE(SUM(l.debit), 0) AS debit,
|
||||||
COALESCE(SUM(l.credit), 0) AS credit
|
COALESCE(SUM(l.credit), 0) AS credit
|
||||||
FROM journal_lines l
|
FROM journal_lines l
|
||||||
JOIN journal_entries j ON j.journal_id = l.journal_id
|
JOIN journal_entries j
|
||||||
AND j.is_deleted = false
|
ON j.journal_entry_id = l.journal_entry_id
|
||||||
WHERE l.account_id = %s
|
WHERE l.account_id = %s
|
||||||
AND j.journal_date BETWEEN %s AND %s
|
AND j.entry_date BETWEEN %s AND %s
|
||||||
|
AND j.is_deleted = false
|
||||||
""", (aid, date_from, date_to))
|
""", (aid, date_from, date_to))
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
debit = D(row["debit"])
|
debit = D(row["debit"])
|
||||||
@@ -56,28 +92,107 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
|||||||
|
|
||||||
closing = opening + debit - credit
|
closing = opening + debit - credit
|
||||||
|
|
||||||
total_opening += opening
|
account_data[code] = {
|
||||||
total_debit += debit
|
|
||||||
total_credit += credit
|
|
||||||
total_closing += closing
|
|
||||||
|
|
||||||
result_accounts.append({
|
|
||||||
"account_id": aid,
|
"account_id": aid,
|
||||||
"account_code": acc["account_code"],
|
"account_code": code,
|
||||||
"account_name": acc["account_name"],
|
"account_name": acc["account_name"],
|
||||||
"account_type": acc["account_type"],
|
"account_type": acc["account_type"],
|
||||||
"opening_balance": str(opening),
|
"opening_balance": opening,
|
||||||
"debit": str(debit),
|
"debit": debit,
|
||||||
"credit": str(credit),
|
"credit": credit,
|
||||||
"closing_balance": str(closing),
|
"closing_balance": closing,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 結果リスト(グループ化)
|
||||||
|
result_accounts: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
# 既に挿入された合計行を追跡
|
||||||
|
inserted_totals = set()
|
||||||
|
|
||||||
|
# 大区分の合計を保存(資産・負債・純資産の総合計)
|
||||||
|
grand_total_closing = D(0)
|
||||||
|
|
||||||
|
# 個別科目を追加
|
||||||
|
for code in sorted(account_data.keys()):
|
||||||
|
data = account_data[code]
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": data["account_code"],
|
||||||
|
"account_name": data["account_name"],
|
||||||
|
"account_type": data["account_type"],
|
||||||
|
"opening_balance": str(data["opening_balance"]),
|
||||||
|
"debit": str(data["debit"]),
|
||||||
|
"credit": str(data["credit"]),
|
||||||
|
"closing_balance": str(data["closing_balance"]),
|
||||||
|
"ratio": None, # 後で計算
|
||||||
|
"is_total": False
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# 大区分合計用に総計を積算
|
||||||
|
grand_total_closing += data["closing_balance"]
|
||||||
|
|
||||||
|
# 合計行を挿入(グループ定義の順序で)
|
||||||
|
for group_name, group_codes in ACCOUNT_GROUPS:
|
||||||
|
# このグループにまだ合計行を挿入していない
|
||||||
|
if group_name in inserted_totals:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 現在の科目がこのグループに含まれる
|
||||||
|
if code not in group_codes:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# このグループの全科目を取得
|
||||||
|
group_codes_in_data = [gc for gc in group_codes if gc in account_data]
|
||||||
|
if not group_codes_in_data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# グループの最後の科目コードを取得
|
||||||
|
last_code_in_group = max(group_codes_in_data)
|
||||||
|
|
||||||
|
# 現在の科目がグループの最後の科目の場合
|
||||||
|
if code == last_code_in_group:
|
||||||
|
# グループ合計を計算
|
||||||
|
group_opening = sum(account_data[gc]["opening_balance"] for gc in group_codes_in_data)
|
||||||
|
group_debit = sum(account_data[gc]["debit"] for gc in group_codes_in_data)
|
||||||
|
group_credit = sum(account_data[gc]["credit"] for gc in group_codes_in_data)
|
||||||
|
group_closing = sum(account_data[gc]["closing_balance"] for gc in group_codes_in_data)
|
||||||
|
|
||||||
|
result_accounts.append({
|
||||||
|
"account_code": "",
|
||||||
|
"account_name": group_name,
|
||||||
|
"account_type": "",
|
||||||
|
"opening_balance": str(group_opening),
|
||||||
|
"debit": str(group_debit),
|
||||||
|
"credit": str(group_credit),
|
||||||
|
"closing_balance": str(group_closing),
|
||||||
|
"ratio": None, # 後で計算
|
||||||
|
"is_total": True
|
||||||
|
})
|
||||||
|
|
||||||
|
inserted_totals.add(group_name)
|
||||||
|
|
||||||
|
# 構成比を計算(総資産に対する割合)
|
||||||
|
for acc in result_accounts:
|
||||||
|
closing = D(acc["closing_balance"])
|
||||||
|
if grand_total_closing != 0:
|
||||||
|
ratio = (closing / grand_total_closing) * 100
|
||||||
|
acc["ratio"] = f"{ratio:.2f}"
|
||||||
|
else:
|
||||||
|
acc["ratio"] = "0.00"
|
||||||
|
|
||||||
|
# 総合計を計算
|
||||||
|
total_opening = sum(data["opening_balance"] for data in account_data.values())
|
||||||
|
total_debit = sum(data["debit"] for data in account_data.values())
|
||||||
|
total_credit = sum(data["credit"] for data in account_data.values())
|
||||||
|
total_closing = sum(data["closing_balance"] for data in account_data.values())
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"totals": {
|
"totals": {
|
||||||
"opening_balance": str(total_opening),
|
"opening_balance": str(total_opening),
|
||||||
"debit": str(total_debit),
|
"debit": str(total_debit),
|
||||||
"credit": str(total_credit),
|
"credit": str(total_credit),
|
||||||
"closing_balance": str(total_closing)
|
"closing_balance": str(total_closing),
|
||||||
|
"ratio": "100.00"
|
||||||
},
|
},
|
||||||
"accounts": result_accounts
|
"accounts": result_accounts,
|
||||||
|
"grand_total_closing": str(grand_total_closing)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def get_trial_balance(
|
|||||||
COALESCE(SUM(l.debit), 0) AS debit,
|
COALESCE(SUM(l.debit), 0) AS debit,
|
||||||
COALESCE(SUM(l.credit), 0) AS credit
|
COALESCE(SUM(l.credit), 0) AS credit
|
||||||
FROM journal_lines l
|
FROM journal_lines l
|
||||||
JOIN journal_entries e ON e.journal_id = l.journal_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 1 = 1
|
WHERE 1 = 1
|
||||||
"""
|
"""
|
||||||
|
|||||||
50
backend/sql/insert_opening_balances_2024.sql
Normal file
50
backend/sql/insert_opening_balances_2024.sql
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
-- 2024年度(2024年6月1日~2025年5月31日)期首残高データ
|
||||||
|
-- ★残高試算表_20250724.pdfより
|
||||||
|
|
||||||
|
-- 既存データを削除(2024年度のみ)
|
||||||
|
DELETE FROM opening_balances WHERE fiscal_year = 2024;
|
||||||
|
|
||||||
|
-- 期首残高データの挿入
|
||||||
|
INSERT INTO opening_balances (fiscal_year, account_id, opening_debit, opening_credit) VALUES
|
||||||
|
-- 現金(101)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '101'), 878375, 0),
|
||||||
|
|
||||||
|
-- 普通預金(102)- UFJ/0788058 として登録
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '102'), 11246013, 0),
|
||||||
|
|
||||||
|
-- 売掛金(201)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '201'), 5269872, 0),
|
||||||
|
|
||||||
|
-- 仮払金(203)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '203'), 1284834, 0),
|
||||||
|
|
||||||
|
-- 買掛金(501)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '501'), 0, 9933067),
|
||||||
|
|
||||||
|
-- 未払金(502)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '502'), 0, 840000),
|
||||||
|
|
||||||
|
-- 未払法人税等(504)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '504'), 0, 94700),
|
||||||
|
|
||||||
|
-- 未払消費税等(505)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '505'), 0, 1498400),
|
||||||
|
|
||||||
|
-- 預り金(506)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '506'), 0, 61300),
|
||||||
|
|
||||||
|
-- 資本金(601)
|
||||||
|
(2024, (SELECT account_id FROM accounts WHERE account_code = '601'), 0, 6000000);
|
||||||
|
|
||||||
|
-- 繰越利益剰余金(602)は自動計算されるため入力不要
|
||||||
|
|
||||||
|
-- 確認用クエリ
|
||||||
|
SELECT
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
ob.opening_debit,
|
||||||
|
ob.opening_credit
|
||||||
|
FROM opening_balances ob
|
||||||
|
JOIN accounts a ON ob.account_id = a.account_id
|
||||||
|
WHERE ob.fiscal_year = 2024
|
||||||
|
ORDER BY a.account_code;
|
||||||
67
backend/sql/insert_opening_balances_2025.sql
Normal file
67
backend/sql/insert_opening_balances_2025.sql
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
-- 既存の期首残高データを削除
|
||||||
|
DELETE FROM opening_balances;
|
||||||
|
|
||||||
|
-- 2025年度(2025年6月1日~2026年5月31日)期首残高データ
|
||||||
|
-- ★残高試算表_20250724.pdf の「当期残高」列より(2025年5月31日時点)
|
||||||
|
|
||||||
|
INSERT INTO opening_balances (fiscal_year, account_id, opening_debit, opening_credit) VALUES
|
||||||
|
-- 現金(101): 0
|
||||||
|
|
||||||
|
-- 普通預金(102): 3,231,129
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '102'), 3231129, 0),
|
||||||
|
|
||||||
|
-- 売掛金(201): 3,083,000
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '201'), 3083000, 0),
|
||||||
|
|
||||||
|
-- 未収入金(202): 17,842,822(短期貸付金の代わり)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '202'), 17842822, 0),
|
||||||
|
|
||||||
|
-- 前渡金(203 仮払金): 387,400
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '203'), 387400, 0),
|
||||||
|
|
||||||
|
-- 工具器具備品(406): 345,383
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '406'), 345383, 0),
|
||||||
|
|
||||||
|
-- 差入保証金(410): 220,000
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '410'), 220000, 0),
|
||||||
|
|
||||||
|
-- 買掛金(501): 9,376,260(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '501'), 0, 9376260),
|
||||||
|
|
||||||
|
-- 未払金(502): 0
|
||||||
|
|
||||||
|
-- 未払費用(503): 1,087,312(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '503'), 0, 1087312),
|
||||||
|
|
||||||
|
-- 未払法人税等(504): 1,525,500(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '504'), 0, 1525500),
|
||||||
|
|
||||||
|
-- 未払消費税等(505): 1,245,200(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '505'), 0, 1245200),
|
||||||
|
|
||||||
|
-- 預り金(506): 669,316(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '506'), 0, 669316),
|
||||||
|
|
||||||
|
-- 資本金(601): 6,000,000(貸方)
|
||||||
|
(2025, (SELECT account_id FROM accounts WHERE account_code = '601'), 0, 6000000);
|
||||||
|
|
||||||
|
-- 繰越利益剰余金(602): 5,206,146 は自動計算されるため入力不要
|
||||||
|
|
||||||
|
-- 確認用クエリ
|
||||||
|
SELECT
|
||||||
|
a.account_code,
|
||||||
|
a.account_name,
|
||||||
|
ob.opening_debit,
|
||||||
|
ob.opening_credit
|
||||||
|
FROM opening_balances ob
|
||||||
|
JOIN accounts a ON ob.account_id = a.account_id
|
||||||
|
WHERE ob.fiscal_year = 2025
|
||||||
|
ORDER BY a.account_code;
|
||||||
|
|
||||||
|
-- 合計確認
|
||||||
|
SELECT
|
||||||
|
SUM(opening_debit) as total_debit,
|
||||||
|
SUM(opening_credit) as total_credit,
|
||||||
|
SUM(opening_debit) - SUM(opening_credit) as diff
|
||||||
|
FROM opening_balances
|
||||||
|
WHERE fiscal_year = 2025;
|
||||||
@@ -52,7 +52,13 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>仕訳日</label>
|
<label>仕訳日</label>
|
||||||
<input type="date" id="entryDate" onchange="onEntryDateChanged()" />
|
<input
|
||||||
|
type="date"
|
||||||
|
id="entryDate"
|
||||||
|
min="1900-01-01"
|
||||||
|
max="2999-12-31"
|
||||||
|
onchange="onEntryDateChanged()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -60,9 +66,11 @@
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="description"
|
id="description"
|
||||||
style="flex: 1"
|
list="descriptionHistory"
|
||||||
|
style="flex: 3; min-width: 600px"
|
||||||
placeholder="例:売上入金/経費支払 など"
|
placeholder="例:売上入金/経費支払 など"
|
||||||
/>
|
/>
|
||||||
|
<datalist id="descriptionHistory"></datalist>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -100,6 +108,8 @@
|
|||||||
<button onclick="submitJournal()">登録</button>
|
<button onclick="submitJournal()">登録</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr style="margin: 40px 0; border: none; border-top: 2px solid #ddd" />
|
||||||
|
|
||||||
<h3>月次锁定管理</h3>
|
<h3>月次锁定管理</h3>
|
||||||
|
|
||||||
<div style="margin-bottom: 8px">
|
<div style="margin-bottom: 8px">
|
||||||
@@ -148,6 +158,43 @@
|
|||||||
🔒 该月份已锁定,不能编辑仕訳
|
🔒 该月份已锁定,不能编辑仕訳
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ========================================
|
||||||
|
仕訳検索セクション(journal-list.htmlから統合)
|
||||||
|
======================================== -->
|
||||||
|
<hr style="margin: 40px 0; border: none; border-top: 2px solid #ddd" />
|
||||||
|
|
||||||
|
<h2>仕訳検索</h2>
|
||||||
|
|
||||||
|
<div style="margin: 16px 0">
|
||||||
|
<label>期間</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="searchFromDate"
|
||||||
|
min="1900-01-01"
|
||||||
|
max="2999-12-31"
|
||||||
|
/>
|
||||||
|
~
|
||||||
|
<input type="date" id="searchToDate" min="1900-01-01" max="2999-12-31" />
|
||||||
|
|
||||||
|
<label style="margin-left: 16px">摘要</label>
|
||||||
|
<input type="text" id="searchKeyword" />
|
||||||
|
|
||||||
|
<button onclick="searchJournals()" style="margin-left: 8px">検索</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>日付</th>
|
||||||
|
<th>摘要</th>
|
||||||
|
<th>借方合計</th>
|
||||||
|
<th>貸方合計</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="searchResultList"></tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let rowSeq = 0; // 各行唯一ID
|
let rowSeq = 0; // 各行唯一ID
|
||||||
|
|
||||||
@@ -229,10 +276,50 @@
|
|||||||
.slice(0, 10);
|
.slice(0, 10);
|
||||||
|
|
||||||
await onEntryDateChanged();
|
await onEntryDateChanged();
|
||||||
|
|
||||||
|
// 摘要履歴を読み込み
|
||||||
|
loadDescriptionHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// 摘要履歴管理
|
||||||
|
// -----------------------------
|
||||||
|
function loadDescriptionHistory() {
|
||||||
|
const history = JSON.parse(
|
||||||
|
localStorage.getItem("descriptionHistory") || "[]"
|
||||||
|
);
|
||||||
|
const datalist = document.getElementById("descriptionHistory");
|
||||||
|
datalist.innerHTML = "";
|
||||||
|
|
||||||
|
history.forEach((desc) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = desc;
|
||||||
|
datalist.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDescriptionToHistory(description) {
|
||||||
|
if (!description || description.trim() === "") return;
|
||||||
|
|
||||||
|
let history = JSON.parse(
|
||||||
|
localStorage.getItem("descriptionHistory") || "[]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 重複を避け、先頭に追加
|
||||||
|
history = history.filter((d) => d !== description);
|
||||||
|
history.unshift(description);
|
||||||
|
|
||||||
|
// 最大20件まで保存
|
||||||
|
if (history.length > 20) {
|
||||||
|
history = history.slice(0, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("descriptionHistory", JSON.stringify(history));
|
||||||
|
loadDescriptionHistory();
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// 仮払/仮受 セレクタ
|
// 仮払/仮受 セレクタ
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
@@ -490,6 +577,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await res.json();
|
const result = await res.json();
|
||||||
|
|
||||||
|
// 摘要を履歴に保存
|
||||||
|
saveDescriptionToHistory(description);
|
||||||
|
|
||||||
alert(`登録完了(ID=${result.journal_entry_id})`);
|
alert(`登録完了(ID=${result.journal_entry_id})`);
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
@@ -664,6 +755,256 @@
|
|||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 仕訳検索機能(journal-list.htmlから統合)
|
||||||
|
// ========================================
|
||||||
|
async function searchJournals() {
|
||||||
|
const from = document.getElementById("searchFromDate").value;
|
||||||
|
const to = document.getElementById("searchToDate").value;
|
||||||
|
const key = document.getElementById("searchKeyword").value;
|
||||||
|
|
||||||
|
// 検索条件を保存
|
||||||
|
localStorage.setItem(
|
||||||
|
"journalSearchConditions",
|
||||||
|
JSON.stringify({
|
||||||
|
fromDate: from,
|
||||||
|
toDate: to,
|
||||||
|
keyword: key,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const qs = new URLSearchParams();
|
||||||
|
if (from) qs.append("from_date", from);
|
||||||
|
if (to) qs.append("to_date", to);
|
||||||
|
if (key) qs.append("keyword", key);
|
||||||
|
|
||||||
|
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const tbody = document.getElementById("searchResultList");
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
|
||||||
|
data.forEach((e) => {
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${e.entry_date}</td>
|
||||||
|
<td>${e.description}</td>
|
||||||
|
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||||
|
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="viewJournal(${e.journal_entry_id})">表示</button>
|
||||||
|
<button onclick="copyJournal(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})" style="color: green;">複製</button>
|
||||||
|
<button onclick="reverseAndEdit(${e.journal_entry_id})">修正</button>
|
||||||
|
<button onclick="deleteJournalEntry(${
|
||||||
|
e.journal_entry_id
|
||||||
|
})" style="color: red;">削除</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewJournal(id) {
|
||||||
|
window.open(`journal-view.html?id=${id}`, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 仕訳複製:既存の仕訳を入力フォームにコピー
|
||||||
|
// --------------------------------------------------
|
||||||
|
async function copyJournal(id) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API}/journal-entries/${id}`);
|
||||||
|
if (!res.ok) {
|
||||||
|
alert("仕訳の取得に失敗しました");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
// 日付は今日の日付をデフォルトに(必要に応じて変更可能)
|
||||||
|
document.getElementById("entryDate").value = new Date()
|
||||||
|
.toISOString()
|
||||||
|
.slice(0, 10);
|
||||||
|
|
||||||
|
// 摘要をコピー
|
||||||
|
document.getElementById("description").value = data.description || "";
|
||||||
|
|
||||||
|
// 既存の行をすべてクリア(税行も含む)
|
||||||
|
const tbody = document.querySelector("#linesTable tbody");
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
rowSeq = 0;
|
||||||
|
|
||||||
|
// 仕訳明細をコピー
|
||||||
|
if (data.lines && data.lines.length > 0) {
|
||||||
|
data.lines.forEach((line) => {
|
||||||
|
const tr = addRow();
|
||||||
|
|
||||||
|
// 科目を設定
|
||||||
|
const accountSelect = tr.querySelector(".account");
|
||||||
|
if (accountSelect) {
|
||||||
|
accountSelect.value = line.account_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 借方・貸方金額を設定
|
||||||
|
const debitInput = tr.querySelector(".debit");
|
||||||
|
const creditInput = tr.querySelector(".credit");
|
||||||
|
if (debitInput && line.debit) {
|
||||||
|
debitInput.value = line.debit;
|
||||||
|
}
|
||||||
|
if (creditInput && line.credit) {
|
||||||
|
creditInput.value = line.credit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 税区分を設定
|
||||||
|
const taxTypeSelect = tr.querySelector(".taxType");
|
||||||
|
const taxDirSelect = tr.querySelector(".taxDirection");
|
||||||
|
|
||||||
|
if (line.tax_rate && taxTypeSelect) {
|
||||||
|
taxTypeSelect.value = String(line.tax_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.tax_direction && taxDirSelect) {
|
||||||
|
taxDirSelect.value = line.tax_direction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ページ上部の仕訳入力フォームにスクロール
|
||||||
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
|
|
||||||
|
alert(
|
||||||
|
"仕訳を複製しました。日付や金額などを修正して登録してください。"
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
alert(`エラーが発生しました: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 修正仕訳:逆仕訳を作って修正画面へ遷移
|
||||||
|
// --------------------------------------------------
|
||||||
|
async function reverseAndEdit(id) {
|
||||||
|
if (
|
||||||
|
!confirm(
|
||||||
|
"この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ① 逆仕訳を作成
|
||||||
|
const res = await fetch(`${API}/journal-entries/${id}/reverse`, {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
alert(
|
||||||
|
data.detail || `逆仕訳の作成に失敗しました (HTTP ${res.status})`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
alert(
|
||||||
|
`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`
|
||||||
|
);
|
||||||
|
|
||||||
|
// ② 元仕訳内容を取得して修正画面へ遷移
|
||||||
|
const srcRes = await fetch(`${API}/journal-entries/${id}`);
|
||||||
|
if (!srcRes.ok) {
|
||||||
|
alert("元仕訳の取得に失敗しました");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcData = await srcRes.json();
|
||||||
|
localStorage.setItem("editSource", JSON.stringify(srcData));
|
||||||
|
|
||||||
|
// 修正画面を開く
|
||||||
|
window.open("journal-edit.html", "_blank");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
alert(`エラーが発生しました: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------
|
||||||
|
// 仕訳削除
|
||||||
|
// --------------------------------------------------
|
||||||
|
async function deleteJournalEntry(id) {
|
||||||
|
const reason = prompt("削除理由を入力してください:");
|
||||||
|
if (!reason || reason.trim() === "") {
|
||||||
|
alert("削除理由の入力が必要です");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm("この仕訳を削除してもよろしいですか?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${API}/journal-entries/${id}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ deleted_reason: reason }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
alert(data.detail || "削除に失敗しました");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert("削除しました");
|
||||||
|
searchJournals(); // リストを再読み込み
|
||||||
|
}
|
||||||
|
|
||||||
|
// ページロード後に前回の検索条件を復元
|
||||||
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// デフォルト期間を設定(5/31決算:6月1日~現在)
|
||||||
|
const today = new Date();
|
||||||
|
const currentYear = today.getFullYear();
|
||||||
|
const currentMonth = today.getMonth() + 1; // 1-12
|
||||||
|
|
||||||
|
let fiscalStartYear;
|
||||||
|
if (currentMonth >= 6) {
|
||||||
|
// 6月以降:当年6月1日から
|
||||||
|
fiscalStartYear = currentYear;
|
||||||
|
} else {
|
||||||
|
// 1-5月:前年6月1日から
|
||||||
|
fiscalStartYear = currentYear - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultFromDate = `${fiscalStartYear}-06-01`;
|
||||||
|
const defaultToDate = today.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const savedConditions = localStorage.getItem("journalSearchConditions");
|
||||||
|
if (savedConditions) {
|
||||||
|
const conditions = JSON.parse(savedConditions);
|
||||||
|
if (conditions.fromDate)
|
||||||
|
document.getElementById("searchFromDate").value =
|
||||||
|
conditions.fromDate;
|
||||||
|
else
|
||||||
|
document.getElementById("searchFromDate").value = defaultFromDate;
|
||||||
|
|
||||||
|
if (conditions.toDate)
|
||||||
|
document.getElementById("searchToDate").value = conditions.toDate;
|
||||||
|
else document.getElementById("searchToDate").value = defaultToDate;
|
||||||
|
|
||||||
|
if (conditions.keyword)
|
||||||
|
document.getElementById("searchKeyword").value = conditions.keyword;
|
||||||
|
|
||||||
|
// 自動的に検索を実行
|
||||||
|
searchJournals();
|
||||||
|
} else {
|
||||||
|
// 保存された条件がない場合はデフォルト値を設定
|
||||||
|
document.getElementById("searchFromDate").value = defaultFromDate;
|
||||||
|
document.getElementById("searchToDate").value = defaultToDate;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,24 +2,95 @@ let accounts = [];
|
|||||||
let retainedEarnings = null;
|
let retainedEarnings = null;
|
||||||
let isLocked = false;
|
let isLocked = false;
|
||||||
|
|
||||||
|
// 科目グループ定義(小計付き)
|
||||||
|
const GROUPS = [
|
||||||
|
{
|
||||||
|
key: "asset",
|
||||||
|
label: "資産",
|
||||||
|
subGroups: [
|
||||||
|
{ label: "現金・預金合計", codes: ["101", "102", "103", "104"] },
|
||||||
|
{ label: "売上債権合計", codes: ["201"] },
|
||||||
|
{
|
||||||
|
label: "流動資産合計",
|
||||||
|
codes: [
|
||||||
|
"101",
|
||||||
|
"102",
|
||||||
|
"103",
|
||||||
|
"104",
|
||||||
|
"201",
|
||||||
|
"202",
|
||||||
|
"203",
|
||||||
|
"204",
|
||||||
|
"205",
|
||||||
|
"206",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "固定資産合計",
|
||||||
|
codes: [
|
||||||
|
"301",
|
||||||
|
"401",
|
||||||
|
"402",
|
||||||
|
"403",
|
||||||
|
"404",
|
||||||
|
"405",
|
||||||
|
"406",
|
||||||
|
"407",
|
||||||
|
"408",
|
||||||
|
"410",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "liability",
|
||||||
|
label: "負債",
|
||||||
|
subGroups: [
|
||||||
|
{ label: "仕入債務合計", codes: ["501"] },
|
||||||
|
{
|
||||||
|
label: "流動負債合計",
|
||||||
|
codes: ["501", "502", "503", "504", "505", "506", "507", "508"],
|
||||||
|
},
|
||||||
|
{ label: "固定負債合計", codes: ["509"] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "equity",
|
||||||
|
label: "純資産",
|
||||||
|
subGroups: [
|
||||||
|
{ label: "株主資本合計", codes: ["601", "602"] },
|
||||||
|
{ label: "純資産合計", codes: ["601", "602"] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function formatNumber(n) {
|
function formatNumber(n) {
|
||||||
return Number(n || 0).toLocaleString();
|
return Number(n || 0).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseNumber(str) {
|
||||||
|
// カンマを除去して数値に変換
|
||||||
|
return Number(String(str).replace(/,/g, "")) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
function recalcTotals() {
|
function recalcTotals() {
|
||||||
let debit = 0;
|
let debit = 0;
|
||||||
let credit = 0;
|
let credit = 0;
|
||||||
|
|
||||||
// 先算:不含繰越利益
|
// 先算:不含繰越利益剰余金
|
||||||
accounts.forEach((a) => {
|
accounts.forEach((a) => {
|
||||||
if (a.account_name === "繰越利益") return;
|
if (a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益")
|
||||||
|
return;
|
||||||
debit += Number(a.opening_debit || 0);
|
debit += Number(a.opening_debit || 0);
|
||||||
credit += Number(a.opening_credit || 0);
|
credit += Number(a.opening_credit || 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
const diff = debit - credit;
|
const diff = debit - credit;
|
||||||
|
|
||||||
// 自动计算 繰越利益
|
console.log("Debug: debit=", debit, "credit=", credit, "diff=", diff);
|
||||||
|
console.log("Debug: retainedEarnings=", retainedEarnings);
|
||||||
|
|
||||||
|
// 自动计算 繰越利益剰余金
|
||||||
if (retainedEarnings) {
|
if (retainedEarnings) {
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
retainedEarnings.opening_debit = 0;
|
retainedEarnings.opening_debit = 0;
|
||||||
@@ -28,6 +99,20 @@ function recalcTotals() {
|
|||||||
retainedEarnings.opening_debit = -diff;
|
retainedEarnings.opening_debit = -diff;
|
||||||
retainedEarnings.opening_credit = 0;
|
retainedEarnings.opening_credit = 0;
|
||||||
}
|
}
|
||||||
|
console.log("Debug: retainedEarnings after calc=", retainedEarnings);
|
||||||
|
|
||||||
|
// DOM入力フィールドを更新
|
||||||
|
const retainedIndex = accounts.indexOf(retainedEarnings);
|
||||||
|
const inputs = document.querySelectorAll("#balanceTable tbody input");
|
||||||
|
inputs.forEach((input) => {
|
||||||
|
const match = input
|
||||||
|
.getAttribute("onchange")
|
||||||
|
?.match(/accounts\[(\d+)\]\.opening_(debit|credit)/);
|
||||||
|
if (match && parseInt(match[1]) === retainedIndex) {
|
||||||
|
const field = match[2]; // "debit" or "credit"
|
||||||
|
input.value = formatNumber(retainedEarnings[`opening_${field}`]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 再算一次(包含繰越利益)
|
// 再算一次(包含繰越利益)
|
||||||
@@ -44,6 +129,35 @@ function recalcTotals() {
|
|||||||
document.getElementById("diff").innerText = formatNumber(
|
document.getElementById("diff").innerText = formatNumber(
|
||||||
finalDebit - finalCredit
|
finalDebit - finalCredit
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 小計行を更新
|
||||||
|
updateSubtotalRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSubtotalRows() {
|
||||||
|
GROUPS.forEach((group) => {
|
||||||
|
group.subGroups.forEach((subGroup) => {
|
||||||
|
const subGroupTotal = accounts
|
||||||
|
.filter((acc) => subGroup.codes.includes(acc.account_code))
|
||||||
|
.reduce(
|
||||||
|
(sum, acc) => ({
|
||||||
|
debit: sum.debit + Number(acc.opening_debit || 0),
|
||||||
|
credit: sum.credit + Number(acc.opening_credit || 0),
|
||||||
|
}),
|
||||||
|
{ debit: 0, credit: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 対応する小計行を探して更新
|
||||||
|
const rows = document.querySelectorAll("#balanceTable tbody tr");
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const labelCell = row.cells[1];
|
||||||
|
if (labelCell && labelCell.textContent === subGroup.label) {
|
||||||
|
row.cells[2].textContent = formatNumber(subGroupTotal.debit);
|
||||||
|
row.cells[3].textContent = formatNumber(subGroupTotal.credit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadOpeningBalances() {
|
async function loadOpeningBalances() {
|
||||||
@@ -52,7 +166,9 @@ async function loadOpeningBalances() {
|
|||||||
const data = await apiGet("/opening-balances", { fiscal_year: year });
|
const data = await apiGet("/opening-balances", { fiscal_year: year });
|
||||||
|
|
||||||
accounts = data;
|
accounts = data;
|
||||||
retainedEarnings = accounts.find((a) => a.account_name === "繰越利益");
|
retainedEarnings = accounts.find(
|
||||||
|
(a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益"
|
||||||
|
);
|
||||||
|
|
||||||
const tbody = document.querySelector("#balanceTable tbody");
|
const tbody = document.querySelector("#balanceTable tbody");
|
||||||
tbody.innerHTML = "";
|
tbody.innerHTML = "";
|
||||||
@@ -67,28 +183,62 @@ async function loadOpeningBalances() {
|
|||||||
`;
|
`;
|
||||||
tbody.appendChild(headerTr);
|
tbody.appendChild(headerTr);
|
||||||
|
|
||||||
accounts.forEach((a, i) => {
|
const groupAccounts = accounts.filter((a) => a.account_type === group.key);
|
||||||
if (a.account_type !== group.key) return;
|
|
||||||
|
|
||||||
const isRetained = a.account_name === "繰越利益";
|
groupAccounts.forEach((a, idx) => {
|
||||||
|
const i = accounts.indexOf(a);
|
||||||
|
const isRetained =
|
||||||
|
a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益";
|
||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${a.account_code}</td>
|
<td>${a.account_code}</td>
|
||||||
<td>${a.account_name}</td>
|
<td>${a.account_name}</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" value="${a.opening_debit}"
|
<input type="text" value="${formatNumber(a.opening_debit)}"
|
||||||
${isRetained ? "readonly" : ""}
|
${isRetained ? "readonly" : ""}
|
||||||
onchange="accounts[${i}].opening_debit=this.value; recalcTotals();">
|
onchange="accounts[${i}].opening_debit=parseNumber(this.value); this.value=formatNumber(accounts[${i}].opening_debit); recalcTotals();">
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" value="${a.opening_credit}"
|
<input type="text" value="${formatNumber(a.opening_credit)}"
|
||||||
${isRetained ? "readonly" : ""}
|
${isRetained ? "readonly" : ""}
|
||||||
onchange="accounts[${i}].opening_credit=this.value; recalcTotals();">
|
onchange="accounts[${i}].opening_credit=parseNumber(this.value); this.value=formatNumber(accounts[${i}].opening_credit); recalcTotals();">
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
|
|
||||||
|
// 小計行の追加
|
||||||
|
group.subGroups.forEach((subGroup) => {
|
||||||
|
const isLastInSubGroup =
|
||||||
|
subGroup.codes.includes(a.account_code) &&
|
||||||
|
!groupAccounts
|
||||||
|
.slice(idx + 1)
|
||||||
|
.some((nextAcc) => subGroup.codes.includes(nextAcc.account_code));
|
||||||
|
|
||||||
|
if (isLastInSubGroup) {
|
||||||
|
const subGroupTotal = accounts
|
||||||
|
.filter((acc) => subGroup.codes.includes(acc.account_code))
|
||||||
|
.reduce(
|
||||||
|
(sum, acc) => ({
|
||||||
|
debit: sum.debit + Number(acc.opening_debit || 0),
|
||||||
|
credit: sum.credit + Number(acc.opening_credit || 0),
|
||||||
|
}),
|
||||||
|
{ debit: 0, credit: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalTr = document.createElement("tr");
|
||||||
|
totalTr.style.backgroundColor = "#d0d0d0";
|
||||||
|
totalTr.style.fontWeight = "bold";
|
||||||
|
totalTr.innerHTML = `
|
||||||
|
<td></td>
|
||||||
|
<td>${subGroup.label}</td>
|
||||||
|
<td>${formatNumber(subGroupTotal.debit)}</td>
|
||||||
|
<td>${formatNumber(subGroupTotal.credit)}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(totalTr);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,9 +268,10 @@ async function saveOpeningBalances() {
|
|||||||
const year = Number(document.getElementById("fiscalYear").value);
|
const year = Number(document.getElementById("fiscalYear").value);
|
||||||
|
|
||||||
const balances = accounts
|
const balances = accounts
|
||||||
// ❗ 排除 繰越利益
|
// ❗ 排除 繰越利益剰余金
|
||||||
.filter(
|
.filter(
|
||||||
(a) =>
|
(a) =>
|
||||||
|
a.account_name !== "繰越利益剰余金" &&
|
||||||
a.account_name !== "繰越利益" &&
|
a.account_name !== "繰越利益" &&
|
||||||
(Number(a.opening_debit) !== 0 || Number(a.opening_credit) !== 0)
|
(Number(a.opening_debit) !== 0 || Number(a.opening_credit) !== 0)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,32 +1,178 @@
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
console.log("trial-balance.js 已执行");
|
console.log("trial-balance.js 已执行");
|
||||||
|
|
||||||
const API_URL = "http://127.0.0.1:18080/trial-balance";
|
const API_URL = "http://127.0.0.1:18080/trial-balance";
|
||||||
|
|
||||||
fetch(API_URL)
|
// デフォルト期間を計算(5/31決算:6月開始~現在月末)
|
||||||
.then(r => { if (!r.ok) throw new Error("API 返回错误: " + r.status); return r.json(); })
|
function getDefaultPeriod() {
|
||||||
.then(data => {
|
const today = new Date();
|
||||||
|
const currentYear = today.getFullYear();
|
||||||
|
const currentMonth = today.getMonth() + 1; // 1-12
|
||||||
|
|
||||||
console.log("试算表 API 数据:", data);
|
let fiscalStartYear, fiscalEndYear;
|
||||||
const tbody = document.querySelector("#trialBalanceTable tbody");
|
|
||||||
if (!tbody) { console.error("未找到 #trialBalanceTable tbody"); return; }
|
|
||||||
|
|
||||||
let totalDebit = 0, totalCredit = 0;
|
// 6月~5月が1会計年度
|
||||||
data.accounts.forEach(row => {
|
if (currentMonth >= 6) {
|
||||||
const debit = Number(row.debit ?? 0);
|
// 6月以降:当年6月1日から
|
||||||
const credit = Number(row.credit ?? 0);
|
fiscalStartYear = currentYear;
|
||||||
totalDebit += debit; totalCredit += credit;
|
fiscalEndYear = currentYear;
|
||||||
|
} else {
|
||||||
|
// 1-5月:前年6月1日から
|
||||||
|
fiscalStartYear = currentYear - 1;
|
||||||
|
fiscalEndYear = currentYear;
|
||||||
|
}
|
||||||
|
|
||||||
const tr = document.createElement("tr");
|
const dateFrom = `${fiscalStartYear}-06-01`;
|
||||||
tr.innerHTML = `
|
|
||||||
<td class="left">${row.account_code}</td>
|
// 現在月の末日を計算
|
||||||
<td class="left">${row.account_name}</td>
|
const lastDayOfMonth = new Date(fiscalEndYear, currentMonth, 0).getDate();
|
||||||
<td>${debit.toLocaleString()}</td>
|
const dateTo = `${fiscalEndYear}-${String(currentMonth).padStart(
|
||||||
<td>${credit.toLocaleString()}</td>`;
|
2,
|
||||||
tbody.appendChild(tr);
|
"0"
|
||||||
|
)}-${String(lastDayOfMonth).padStart(2, "0")}`;
|
||||||
|
|
||||||
|
return { dateFrom, dateTo };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 試算表データを読み込む関数
|
||||||
|
function loadTrialBalance(dateFrom, dateTo) {
|
||||||
|
const url = `${API_URL}?date_from=${dateFrom}&date_to=${dateTo}`;
|
||||||
|
console.log("読み込み URL:", url);
|
||||||
|
|
||||||
|
fetch(url)
|
||||||
|
.then((r) => {
|
||||||
|
if (!r.ok) throw new Error("API 返回错误: " + r.status);
|
||||||
|
return r.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
console.log("試算表 API データ:", data);
|
||||||
|
|
||||||
|
// 期間情報を更新
|
||||||
|
updatePeriodInfo(dateFrom, dateTo);
|
||||||
|
|
||||||
|
const tbody = document.querySelector("#trialBalanceTable tbody");
|
||||||
|
if (!tbody) {
|
||||||
|
console.error("未找到 #trialBalanceTable tbody");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody.innerHTML = "";
|
||||||
|
|
||||||
|
let totalOpening = 0;
|
||||||
|
let totalDebit = 0;
|
||||||
|
let totalCredit = 0;
|
||||||
|
let totalClosing = 0;
|
||||||
|
|
||||||
|
data.accounts.forEach((row) => {
|
||||||
|
const debit = Number(row.debit ?? 0);
|
||||||
|
const credit = Number(row.credit ?? 0);
|
||||||
|
const opening = Number(row.opening_balance ?? 0);
|
||||||
|
const closing = Number(row.closing_balance ?? 0);
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// 行タイプ判定
|
||||||
|
// =========================
|
||||||
|
const isTotalRow = !row.account_code; // null / "" / undefined 全覆盖
|
||||||
|
|
||||||
|
// 明细行だけ合計に含める
|
||||||
|
if (!isTotalRow) {
|
||||||
|
totalOpening += opening;
|
||||||
|
totalDebit += debit;
|
||||||
|
totalCredit += credit;
|
||||||
|
totalClosing += closing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
|
||||||
|
if (isTotalRow) {
|
||||||
|
tr.classList.add("total-row");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratio = row.ratio ?? "0.00";
|
||||||
|
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td class="left">${row.account_code ?? ""}</td>
|
||||||
|
<td class="left">${row.account_name ?? ""}</td>
|
||||||
|
<td class="right">${opening ? opening.toLocaleString() : ""}</td>
|
||||||
|
<td class="right">${debit ? debit.toLocaleString() : ""}</td>
|
||||||
|
<td class="right">${credit ? credit.toLocaleString() : ""}</td>
|
||||||
|
<td class="right">${closing ? closing.toLocaleString() : ""}</td>
|
||||||
|
<td class="right">${ratio}</td>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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";
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("試算表読取失敗:", err);
|
||||||
|
alert("試算表読取失敗、コンソールログを確認してください");
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById("totalDebit").textContent = totalDebit.toLocaleString();
|
// 期間情報を更新する関数
|
||||||
document.getElementById("totalCredit").textContent = totalCredit.toLocaleString();
|
function updatePeriodInfo(dateFrom, dateTo) {
|
||||||
})
|
const fromDate = new Date(dateFrom);
|
||||||
.catch(err => { console.error("試算表读取失败:", err); alert("試算表读取失败,请查看控制台日志"); });
|
const toDate = new Date(dateTo);
|
||||||
|
|
||||||
|
const fromYear = fromDate.getFullYear() - 2018; // 令和変換(2019年=令和1年)
|
||||||
|
const toYear = toDate.getFullYear() - 2018;
|
||||||
|
|
||||||
|
const periodText = `令和${fromYear}年 ${
|
||||||
|
fromDate.getMonth() + 1
|
||||||
|
}月 ${fromDate.getDate()}日 ~ 令和${toYear}年 ${
|
||||||
|
toDate.getMonth() + 1
|
||||||
|
}月 ${toDate.getDate()}日`;
|
||||||
|
document.getElementById("periodInfo").textContent = periodText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 検索ボタンのイベント
|
||||||
|
document.getElementById("btnSearch").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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 全年度表示ボタンのイベント
|
||||||
|
document.getElementById("btnFullYear").addEventListener("click", () => {
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const dateFrom = `${currentYear}-01-01`;
|
||||||
|
const dateTo = `${currentYear}-12-31`;
|
||||||
|
|
||||||
|
document.getElementById("dateFrom").value = dateFrom;
|
||||||
|
document.getElementById("dateTo").value = dateTo;
|
||||||
|
|
||||||
|
loadTrialBalance(dateFrom, dateTo);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初期読み込み(5/31決算に対応)
|
||||||
|
const defaultPeriod = getDefaultPeriod();
|
||||||
|
document.getElementById("dateFrom").value = defaultPeriod.dateFrom;
|
||||||
|
document.getElementById("dateTo").value = defaultPeriod.dateTo;
|
||||||
|
|
||||||
|
loadTrialBalance(defaultPeriod.dateFrom, defaultPeriod.dateTo);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,10 +39,9 @@
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
<button onclick="saveOpeningBalances()">保存</button>
|
<button onclick="saveOpeningBalances()">保存</button>
|
||||||
<a href="index.html">← 戻る</a>
|
<button onclick="location.href='trial-balance.html'">← 戻る</button>
|
||||||
|
|
||||||
<script src="js/api.js"></script>
|
<script src="js/api.js"></script>
|
||||||
<script src="js/opening_balance.js"></script>
|
<script src="js/opening_balance.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
s
|
|
||||||
|
|||||||
@@ -5,33 +5,109 @@
|
|||||||
<title>試算表</title>
|
<title>試算表</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: 'MS Gothic', 'Meiryo', sans-serif;
|
||||||
margin: 24px;
|
margin: 24px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.search-form {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.search-form label {
|
||||||
|
margin-right: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.search-form input {
|
||||||
|
margin-right: 15px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border: 1px solid #999;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.search-form button {
|
||||||
|
padding: 6px 20px;
|
||||||
|
margin: 0 5px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.search-form button:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
.search-form button.secondary {
|
||||||
|
background: #2196F3;
|
||||||
|
}
|
||||||
|
.search-form button.secondary:hover {
|
||||||
|
background: #0b7dda;
|
||||||
|
}
|
||||||
|
.period-info {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #999;
|
||||||
padding: 6px 10px;
|
padding: 5px 8px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
th {
|
th {
|
||||||
background: #f5f5f5;
|
background: #e8e8e8;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
td.left {
|
td.left {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
td.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
tr.total-row td {
|
||||||
|
background-color: #d8d8d8 !important;
|
||||||
|
font-weight: bold;
|
||||||
|
border-top: 2px solid #666;
|
||||||
|
}
|
||||||
tfoot td {
|
tfoot td {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: #fafafa;
|
background: #e0e0e0;
|
||||||
|
border-top: 2px solid #333;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>試算表</h2>
|
<h2>残高試算表(貸借・損益)</h2>
|
||||||
|
|
||||||
|
<div class="search-form">
|
||||||
|
<label for="dateFrom">開始年月日:</label>
|
||||||
|
<input type="date" id="dateFrom" />
|
||||||
|
|
||||||
|
<label for="dateTo">終了年月日:</label>
|
||||||
|
<input type="date" id="dateTo" />
|
||||||
|
|
||||||
|
<button id="btnSearch">検索</button>
|
||||||
|
<button id="btnFullYear" class="secondary">全年度表示</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="period-info">
|
||||||
|
<span id="periodInfo">令和7年 1月 1日 ~ 令和7年 12月31日</span>
|
||||||
|
<span style="margin-left: 20px;">単位: 円</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table id="trialBalanceTable">
|
<table id="trialBalanceTable">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -39,9 +115,10 @@
|
|||||||
<th class="left">科目コード</th>
|
<th class="left">科目コード</th>
|
||||||
<th class="left">科目名称</th>
|
<th class="left">科目名称</th>
|
||||||
<th>期首残高</th>
|
<th>期首残高</th>
|
||||||
<th>借方合計</th>
|
<th>借方発生額</th>
|
||||||
<th>贷方合計</th>
|
<th>貸方発生額</th>
|
||||||
<th>期末残高</th>
|
<th>期末残高</th>
|
||||||
|
<th>構成比(%)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -54,54 +131,14 @@
|
|||||||
<td id="totalDebit">0</td>
|
<td id="totalDebit">0</td>
|
||||||
<td id="totalCredit">0</td>
|
<td id="totalCredit">0</td>
|
||||||
<td id="totalClosing">0</td>
|
<td id="totalClosing">0</td>
|
||||||
|
<td id="totalRatio">100.00</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<script>
|
<script src="js/trial-balance.js"></script>
|
||||||
const API_BASE = "http://127.0.0.1:18080"; // 和你后端一致
|
</body>
|
||||||
|
</html>
|
||||||
async function loadTrialBalance() {
|
|
||||||
const res = await fetch(`${API_BASE}/trial-balance`);
|
|
||||||
if (!res.ok) {
|
|
||||||
alert("試算表の取得に失敗しました");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
|
|
||||||
const tbody = document.querySelector("#trialBalanceTable tbody");
|
|
||||||
tbody.innerHTML = "";
|
|
||||||
|
|
||||||
data.accounts.forEach((acc) => {
|
|
||||||
const tr = document.createElement("tr");
|
|
||||||
tr.innerHTML = `
|
|
||||||
<td class="left">${acc.account_code}</td>
|
|
||||||
<td class="left">${acc.account_name}</td>
|
|
||||||
<td>${Number(acc.opening_balance).toLocaleString()}</td>
|
|
||||||
<td>${Number(acc.debit).toLocaleString()}</td>
|
|
||||||
<td>${Number(acc.credit).toLocaleString()}</td>
|
|
||||||
<td>${Number(acc.closing_balance).toLocaleString()}</td>
|
|
||||||
`;
|
|
||||||
tbody.appendChild(tr);
|
|
||||||
});
|
|
||||||
|
|
||||||
// totals
|
|
||||||
document.getElementById("totalOpening").innerText = Number(
|
|
||||||
data.totals.opening_balance
|
|
||||||
).toLocaleString();
|
|
||||||
document.getElementById("totalDebit").innerText = Number(
|
|
||||||
data.totals.debit
|
|
||||||
).toLocaleString();
|
|
||||||
document.getElementById("totalCredit").innerText = Number(
|
|
||||||
data.totals.credit
|
|
||||||
).toLocaleString();
|
|
||||||
document.getElementById("totalClosing").innerText = Number(
|
|
||||||
data.totals.closing_balance
|
|
||||||
).toLocaleString();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadTrialBalance();
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
BIN
reports/pdf/★残高試算表_20250724.pdf
Normal file
BIN
reports/pdf/★残高試算表_20250724.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user