Files
njts-accounting-core/frontend/journal-entry.html
FNOS-WIN11ENT\choshoukaku 88163423e0 修正
2025-12-15 17:57:49 +09:00

261 lines
7.9 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>仕訳入力</title>
<style>
body { font-family: sans-serif; margin: 24px; }
h2 { margin: 0 0 12px; }
table { border-collapse: collapse; width: 100%; margin-top: 12px; }
th, td { border: 1px solid #ccc; padding: 6px; text-align: center; }
th { background: #f5f5f5; }
input, select, button { padding: 6px; }
.right { text-align: right; }
.row { margin: 8px 0; display: flex; gap: 16px; align-items: center; }
.row label { min-width: 120px; }
.tax-row { background: #f0f8ff; }
</style>
</head>
<body>
<h2>仕訳入力</h2>
<div class="row">
<label>仕訳日</label>
<input type="date" id="entryDate" />
</div>
<div class="row">
<label>摘要</label>
<input type="text" id="description" style="flex:1" placeholder="例:売上入金/経費支払 など" />
</div>
<div class="row">
<label>仮払消費税等 勘定</label>
<select id="taxPaidSelect"></select>
<label>仮受消費税等 勘定</label>
<select id="taxReceivedSelect"></select>
</div>
<table id="linesTable">
<thead>
<tr>
<th>科目</th>
<th>借方</th>
<th>貸方</th>
<th>税区分</th>
<th>税方向</th>
<th>操作</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div style="margin-top:10px; display:flex; gap:8px;">
<button onclick="addRow()"> 行追加</button>
<button onclick="submitJournal()">登録</button>
</div>
<script>
const API = "http://127.0.0.1:18080";
let accounts = [];
let taxPaidAccounts = [];
let taxReceivedAccounts = [];
// -----------------------------
// 初期化
// -----------------------------
async function init() {
const res = await fetch(`${API}/accounts`);
if (!res.ok) {
alert("科目一覧の取得に失敗しました");
return;
}
accounts = await res.json();
// 消費税用勘定を抽出(名前は「仮払」「仮受」を含む想定)
// 仮払消費税等:资产 + code 504或你实际的 code
taxPaidAccounts = accounts.filter(
a => a.account_code === "504"
);
// 仮受消費税等:负债 + code 505或你实际的 code
taxReceivedAccounts = accounts.filter(
a => a.account_code === "505"
);
setupTaxSelectors();
addRow();
// 仕訳日:既定で今日
document.getElementById("entryDate").value = new Date().toISOString().slice(0,10);
}
init();
// -----------------------------
// 仮払/仮受 セレクタ
// -----------------------------
function setupTaxSelectors() {
document.getElementById("taxPaidSelect").innerHTML =
`<option value="">-- 選択してください --</option>` +
taxPaidAccounts.map(a =>
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
).join("");
document.getElementById("taxReceivedSelect").innerHTML =
`<option value="">-- 選択してください --</option>` +
taxReceivedAccounts.map(a =>
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
).join("");
}
// -----------------------------
// 行追加
// -----------------------------
function addRow(isTaxRow = false, taxInfo = null) {
const tbody = document.querySelector("#linesTable tbody");
const tr = document.createElement("tr");
if (isTaxRow) tr.classList.add("tax-row");
tr.innerHTML = `
<td>
<select class="account" ${isTaxRow ? "disabled" : ""}>
${accounts.map(a =>
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
).join("")}
</select>
</td>
<td><input type="number" class="debit right" min="0" ${isTaxRow ? "readonly" : ""}></td>
<td><input type="number" class="credit right" min="0" ${isTaxRow ? "readonly" : ""}></td>
<td>
<select class="taxType" ${isTaxRow ? "disabled" : ""}>
<option value="none">対象外</option>
<option value="10">10%</option>
<option value="8">8%</option>
</select>
</td>
<td>
<select class="taxDirection" ${isTaxRow ? "disabled" : ""}>
<option value="none">対象外</option>
<option value="paid">仮払(仕入・経費)</option>
<option value="received">仮受(売上)</option>
</select>
</td>
<td>${isTaxRow ? "" : `<button onclick="removeRow(this)">削除</button>`}</td>
`;
tbody.appendChild(tr);
if (!isTaxRow) {
tr.querySelector(".debit").addEventListener("input", () => handleTax(tr));
tr.querySelector(".credit").addEventListener("input", () => handleTax(tr));
tr.querySelector(".taxType").addEventListener("change", () => handleTax(tr));
tr.querySelector(".taxDirection").addEventListener("change", () => handleTax(tr));
}
if (isTaxRow && taxInfo) {
const acc = tr.querySelector(".account");
acc.value = taxInfo.accountId || "";
if (taxInfo.side === "debit") tr.querySelector(".debit").value = taxInfo.amount;
if (taxInfo.side === "credit") tr.querySelector(".credit").value = taxInfo.amount;
}
}
// -----------------------------
// 税行 自動生成/更新10%/8%のみ、四捨五入)
// -----------------------------
function handleTax(row) {
removeTaxRow(row);
const taxType = row.querySelector(".taxType").value;
const taxDir = row.querySelector(".taxDirection").value;
const debit = Number(row.querySelector(".debit").value || 0);
const credit = Number(row.querySelector(".credit").value || 0);
if (taxType === "none" || taxDir === "none") return;
const baseAmount = debit || credit;
if (!baseAmount) return;
const rate = taxType === "8" ? 0.08 : 0.10;
const taxAmount = Math.round(baseAmount * rate);
if (taxDir === "paid") {
const taxAcc = document.getElementById("taxPaidSelect").value;
if (!taxAcc) { alert("仮払消費税等 勘定を選択してください"); return; }
addRow(true, { accountId: taxAcc, amount: taxAmount, side: "debit" });
}
if (taxDir === "received") {
const taxAcc = document.getElementById("taxReceivedSelect").value;
if (!taxAcc) { alert("仮受消費税等 勘定を選択してください"); return; }
addRow(true, { accountId: taxAcc, amount: taxAmount, side: "credit" });
}
}
// 税行は常に「直後の1行」を使う
function removeTaxRow(row) {
const next = row.nextElementSibling;
if (next && next.classList.contains("tax-row")) next.remove();
}
function removeRow(btn) {
const row = btn.closest("tr");
removeTaxRow(row);
row.remove();
}
// -----------------------------
// 登録(最小可用版)
// -----------------------------
async function submitJournal() {
const entryDate = document.getElementById("entryDate").value;
const description = document.getElementById("description").value.trim();
if (!entryDate) { alert("仕訳日を入力してください"); return; }
// 明細収集
const rows = document.querySelectorAll("#linesTable tbody tr");
const lines = [];
let totalDebit = 0, totalCredit = 0;
rows.forEach(row => {
const accountId = Number(row.querySelector(".account").value);
const debit = Number(row.querySelector(".debit").value || 0);
const credit = Number(row.querySelector(".credit").value || 0);
if (debit === 0 && credit === 0) return;
lines.push({ account_id: accountId, debit: debit, credit: credit });
totalDebit += debit;
totalCredit += credit;
});
if (lines.length === 0) { alert("仕訳明細がありません"); return; }
// 借貸一致チェック(最小)
if (totalDebit !== totalCredit) {
alert(`借方(${totalDebit.toLocaleString()})と貸方(${totalCredit.toLocaleString()})が一致しません`);
return;
}
const payload = { entry_date: entryDate, description, lines };
const res = await fetch(`${API}/journal-entries`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (!res.ok) { alert("登録に失敗しました"); return; }
const result = await res.json();
alert(`登録完了ID=${result.journal_entry_id}`);
location.reload();
}
</script>
</body>
</html>