326 lines
9.1 KiB
JavaScript
326 lines
9.1 KiB
JavaScript
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 = `
|
||
<td>${a.account_code}</td>
|
||
<td>${a.account_name}</td>
|
||
<td>
|
||
<input type="text" value="${formatNumber(a.opening_debit)}"
|
||
${isRetained ? "readonly" : ""}
|
||
onchange="accounts[${idx}].opening_debit=parseNumber(this.value); this.value=formatNumber(accounts[${idx}].opening_debit); recalcTotals();">
|
||
</td>
|
||
<td>
|
||
<input type="text" value="${formatNumber(a.opening_credit)}"
|
||
${isRetained ? "readonly" : ""}
|
||
onchange="accounts[${idx}].opening_credit=parseNumber(this.value); this.value=formatNumber(accounts[${idx}].opening_credit); recalcTotals();">
|
||
</td>
|
||
`;
|
||
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);
|
||
}
|
||
}
|