let accounts = []; let retainedEarnings = null; 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) { 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; const data = await apiGet("/opening-balances", { fiscal_year: year }); accounts = data; retainedEarnings = accounts.find( (a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益", ); const tbody = document.querySelector("#balanceTable tbody"); tbody.innerHTML = ""; GROUPS.forEach((group) => { // 分组标题行 const headerTr = document.createElement("tr"); headerTr.innerHTML = `