let accounts = []; let retainedEarnings = null; let isLocked = false; let isDataLoaded = false; // ★ データ読込済みフラグ(保存時の検証用) // 科目グループ定義(小計付き)- 試算表と同じ順序 const GROUPS = [ { key: "asset", label: "資産", subGroups: [ { label: "現金・預金合計", codes: ["101", "102", "103", "104", "105"] }, { label: "売上債権合計", codes: ["201", "202"] }, { label: "有価証券合計", codes: ["301"] }, { label: "棚卸資産合計", codes: ["210"] }, { label: "他流動資産合計", codes: ["203", "204", "205", "206", "207", "208"], }, { label: "流動資産合計", codes: [ "101", "102", "103", "104", "105", "201", "202", "203", "204", "205", "206", "207", "208", "210", "301", ], }, { label: "有形固定資産合計", codes: ["401", "402", "403", "404", "405", "406", "407"], }, { label: "無形固定資産合計", codes: ["408"] }, { label: "投資その他の資産合計", codes: ["301", "410"] }, { label: "固定資産合計", codes: [ "301", "401", "402", "403", "404", "405", "406", "407", "408", "410", ], }, { label: "資産合計", codes: [ "101", "102", "103", "104", "105", "201", "202", "203", "204", "205", "206", "207", "208", "210", "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"] }, { label: "負債合計", codes: ["501", "502", "503", "504", "505", "506", "507", "508", "509"], }, ], }, { key: "equity", label: "純資産", subGroups: [ { label: "資本金合計", codes: ["601"] }, { label: "純資産合計", codes: ["601", "602"] }, ], }, ]; function formatNumber(n) { return Number(n || 0).toLocaleString(); } function parseNumber(str) { // カンマを除去して数値に変換 return Number(String(str).replace(/,/g, "")) || 0; } function recalcTotals() { let debit = 0; let credit = 0; // 先算:不含繰越利益剰余金 accounts.forEach((a) => { if (a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益") return; debit += Number(a.opening_debit || 0); credit += Number(a.opening_credit || 0); }); const diff = debit - credit; console.log("Debug: debit=", debit, "credit=", credit, "diff=", diff); console.log("Debug: retainedEarnings=", retainedEarnings); // 自动计算 繰越利益剰余金 if (retainedEarnings) { if (diff > 0) { retainedEarnings.opening_debit = 0; retainedEarnings.opening_credit = diff; } else { retainedEarnings.opening_debit = -diff; 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}`]); } }); } // 再算一次(包含繰越利益) let finalDebit = 0; let finalCredit = 0; accounts.forEach((a) => { finalDebit += Number(a.opening_debit || 0); finalCredit += Number(a.opening_credit || 0); }); document.getElementById("totalDebit").innerText = formatNumber(finalDebit); document.getElementById("totalCredit").innerText = formatNumber(finalCredit); document.getElementById("diff").innerText = formatNumber( 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() { const year = document.getElementById("fiscalYear").value; console.log(`🔄 Loading opening balances for year: ${year}`); try { const data = await apiGet("/opening-balances", { fiscal_year: year }); console.log(`✓ Received data:`, data); console.log(`✓ Total accounts: ${data.length}`); accounts = data; retainedEarnings = accounts.find( (a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益", ); const tbody = document.querySelector("#balanceTable tbody"); console.log(`📌 tbody element:`, tbody); tbody.innerHTML = ""; // シンプルに全科目を表示(グループ分けは後で) console.log(`🔄 Starting to render ${data.length} accounts...`); accounts.forEach((a, idx) => { const isRetained = a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益"; const tr = document.createElement("tr"); tr.innerHTML = ` ${a.account_code} ${a.account_name} `; tbody.appendChild(tr); }); console.log(`✅ Table rendered!`, tbody.innerHTML.substring(0, 100)); isDataLoaded = true; // ★ データ読込済みフラグを設定 recalcTotals(); } catch (e) { console.error("❌ Error loading opening balances:", e); alert(`読込エラー: ${e.message}`); } } async function saveOpeningBalances() { // ★ 重要:データを読込まずに保存するのを防止 if (!isDataLoaded) { alert("先に「読込」ボタンで期首残高を読み込んでください。"); return; } if (isLocked) { alert("この会計年度の期首残高は確定されているため、保存できません。"); return; } const year = Number(document.getElementById("fiscalYear").value); const balances = accounts // ❗ 排除 繰越利益剰余金 .filter( (a) => a.account_name !== "繰越利益剰余金" && a.account_name !== "繰越利益" && (Number(a.opening_debit) !== 0 || Number(a.opening_credit) !== 0), ) .map((a) => ({ account_id: a.account_id, debit: Number(a.opening_debit || 0), credit: Number(a.opening_credit || 0), })); // ★ 重要:データが全て0の場合、保存を拒否 if (balances.length === 0) { alert("全科目の残高が0です。期首残高データを入力してください。"); return; } try { await fetch(`/opening-balances`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ fiscal_year: year, balances: balances, }), }).then(async (res) => { if (!res.ok) { const data = await res.json(); throw new Error(data.detail); } }); alert("期首残高を保存しました"); // メインメニューに移動 location.href = "index.html"; } catch (e) { alert(e.message); } }