Files
njts-accounting-core/frontend/js/opening_balance.js
2026-02-21 10:51:25 +09:00

326 lines
9.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}