153 lines
4.0 KiB
JavaScript
153 lines
4.0 KiB
JavaScript
let accounts = [];
|
|
let retainedEarnings = null;
|
|
let isLocked = false;
|
|
|
|
function formatNumber(n) {
|
|
return Number(n || 0).toLocaleString();
|
|
}
|
|
|
|
function recalcTotals() {
|
|
let debit = 0;
|
|
let credit = 0;
|
|
|
|
// 先算:不含繰越利益
|
|
accounts.forEach((a) => {
|
|
if (a.account_name === "繰越利益") return;
|
|
debit += Number(a.opening_debit || 0);
|
|
credit += Number(a.opening_credit || 0);
|
|
});
|
|
|
|
const diff = debit - credit;
|
|
|
|
// 自动计算 繰越利益
|
|
if (retainedEarnings) {
|
|
if (diff > 0) {
|
|
retainedEarnings.opening_debit = 0;
|
|
retainedEarnings.opening_credit = diff;
|
|
} else {
|
|
retainedEarnings.opening_debit = -diff;
|
|
retainedEarnings.opening_credit = 0;
|
|
}
|
|
}
|
|
|
|
// 再算一次(包含繰越利益)
|
|
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
|
|
);
|
|
}
|
|
|
|
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 === "繰越利益");
|
|
|
|
const tbody = document.querySelector("#balanceTable tbody");
|
|
tbody.innerHTML = "";
|
|
|
|
GROUPS.forEach((group) => {
|
|
// 分组标题行
|
|
const headerTr = document.createElement("tr");
|
|
headerTr.innerHTML = `
|
|
<td colspan="4" style="background:#ddd; font-weight:bold;">
|
|
${group.label}
|
|
</td>
|
|
`;
|
|
tbody.appendChild(headerTr);
|
|
|
|
accounts.forEach((a, i) => {
|
|
if (a.account_type !== group.key) return;
|
|
|
|
const isRetained = a.account_name === "繰越利益";
|
|
const tr = document.createElement("tr");
|
|
|
|
tr.innerHTML = `
|
|
<td>${a.account_code}</td>
|
|
<td>${a.account_name}</td>
|
|
<td>
|
|
<input type="number" value="${a.opening_debit}"
|
|
${isRetained ? "readonly" : ""}
|
|
onchange="accounts[${i}].opening_debit=this.value; recalcTotals();">
|
|
</td>
|
|
<td>
|
|
<input type="number" value="${a.opening_credit}"
|
|
${isRetained ? "readonly" : ""}
|
|
onchange="accounts[${i}].opening_credit=this.value; recalcTotals();">
|
|
</td>
|
|
`;
|
|
|
|
tbody.appendChild(tr);
|
|
});
|
|
});
|
|
|
|
// 年度锁定チェック
|
|
fetch(
|
|
`http://127.0.0.1:18080/opening-balances/lock-status?fiscal_year=${year}`
|
|
)
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
isLocked = data.is_locked;
|
|
document.querySelector(
|
|
"button[onclick='saveOpeningBalances()']"
|
|
).disabled = isLocked;
|
|
if (isLocked) {
|
|
alert("この会計年度の期首残高は確定されています。");
|
|
}
|
|
});
|
|
|
|
recalcTotals();
|
|
}
|
|
|
|
async function saveOpeningBalances() {
|
|
if (isLocked) {
|
|
alert("この会計年度の期首残高は確定されているため、保存できません。");
|
|
return;
|
|
}
|
|
const year = Number(document.getElementById("fiscalYear").value);
|
|
|
|
const balances = accounts
|
|
// ❗ 排除 繰越利益
|
|
.filter(
|
|
(a) =>
|
|
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),
|
|
}));
|
|
|
|
try {
|
|
await fetch("http://127.0.0.1:18080/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("期首残高を保存しました");
|
|
} catch (e) {
|
|
alert(e.message);
|
|
}
|
|
}
|