Files
njts-accounting-core/frontend/journal-edit.html
2026-01-16 09:50:21 +09:00

257 lines
7.3 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;
}
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;
}
.error {
color: #c00;
margin-top: 8px;
}
.ok {
color: #090;
margin-top: 8px;
}
</style>
</head>
<body>
<h2>修正仕訳入力</h2>
<label>仕訳日</label>
<input type="date" id="entryDate" min="1900-01-01" max="9999-12-31" />
<label>摘要</label>
<input type="text" id="description" style="width: 60%" />
<table id="linesTable">
<thead>
<tr>
<th>科目</th>
<th>借方</th>
<th>貸方</th>
<th>操作</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<th>合計</th>
<th id="debitTotal" class="right">0</th>
<th id="creditTotal" class="right">0</th>
<th></th>
</tr>
</tfoot>
</table>
<div style="margin-top: 12px">
<button onclick="addRow()"> 行追加</button>
<button onclick="submitEntry()">修正版仕訳を登録</button>
<button onclick="cancelEdit()" style="background-color: #f0f0f0">
キャンセル
</button>
</div>
<div id="result"></div>
<script>
const API = "http://127.0.0.1:18080";
let accounts = [];
// ------------------------------------
// 初期化
// ------------------------------------
async function init() {
// 科目取得
const res = await fetch(`${API}/accounts`);
const data = await res.json();
accounts = data.items ?? data;
// 元仕訳取得
const src = localStorage.getItem("editSource");
if (!src) {
showError("修正元の仕訳データが見つかりません");
return;
}
const srcData = JSON.parse(src);
// ヘッダ反映
document.getElementById("entryDate").value = srcData.entry_date;
document.getElementById(
"description"
).value = `【修正後】${srcData.description}`;
// 明細反映
srcData.lines.forEach((l) => addRow(l));
recalc();
}
init();
// ------------------------------------
// 行操作
// ------------------------------------
function addRow(line = null) {
const tbody = document.querySelector("#linesTable tbody");
const tr = document.createElement("tr");
tr.innerHTML = `
<td>
<select>
${accounts
.map(
(a) =>
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
)
.join("")}
</select>
</td>
<td><input type="number" min="0"></td>
<td><input type="number" min="0"></td>
<td><button onclick="removeRow(this)">削除</button></td>
`;
tbody.appendChild(tr);
if (line) {
tr.querySelector("select").value = line.account_id;
tr.querySelectorAll("input")[0].value = line.debit || "";
tr.querySelectorAll("input")[1].value = line.credit || "";
}
// 入力時に合計を自動更新
tr.querySelectorAll("input").forEach((input) => {
input.addEventListener("input", recalc);
input.addEventListener("change", recalc);
});
}
function removeRow(btn) {
btn.closest("tr").remove();
recalc();
}
// ------------------------------------
// 合計再計算
// ------------------------------------
function recalc() {
let d = 0,
c = 0;
document.querySelectorAll("#linesTable tbody tr").forEach((tr) => {
d += Number(tr.querySelectorAll("input")[0].value || 0);
c += Number(tr.querySelectorAll("input")[1].value || 0);
});
document.getElementById("debitTotal").textContent = d.toLocaleString();
document.getElementById("creditTotal").textContent = c.toLocaleString();
}
// ------------------------------------
// 登録
// ------------------------------------
async function submitEntry() {
const result = document.getElementById("result");
result.textContent = "";
result.className = "";
const entryDate = document.getElementById("entryDate").value;
const desc = document.getElementById("description").value;
if (!entryDate || !desc) {
showError("日付と摘要は必須です");
return;
}
const lines = [];
let d = 0,
c = 0;
document.querySelectorAll("#linesTable tbody tr").forEach((tr) => {
const accountId = Number(tr.querySelector("select").value);
const debit = Number(tr.querySelectorAll("input")[0].value || 0);
const credit = Number(tr.querySelectorAll("input")[1].value || 0);
if (debit === 0 && credit === 0) return;
lines.push({ account_id: accountId, debit, credit });
d += debit;
c += credit;
});
if (lines.length < 2) return showError("仕訳行は2行以上必要です");
if (d !== c) return showError("借方合計と貸方合計が一致していません");
// 逆仕訳IDを親IDとして取得逆仕訳が存在する場合
const src = localStorage.getItem("editSource");
const srcData = src ? JSON.parse(src) : null;
// 【修正後】仕訳のparent_entry_idは、直前に作成された逆仕訳のID
const parentEntryId = srcData?.reversed_journal_entry_id || null;
const payload = {
entry_date: entryDate,
description: desc,
lines,
tax_paid_account_id: null,
tax_received_account_id: null,
rounding_mode: "floor",
parent_entry_id: parentEntryId,
};
try {
const res = await fetch(`${API}/journal-entries`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json();
if (!res.ok) return showError(data.detail || "登録失敗");
showOk(`修正版仕訳を登録しましたID: ${data.journal_entry_id}`);
localStorage.removeItem("editSource");
} catch {
showError("通信エラー");
}
}
function showError(msg) {
const r = document.getElementById("result");
r.textContent = msg;
r.className = "error";
}
function showOk(msg) {
const r = document.getElementById("result");
r.textContent = msg;
r.className = "ok";
}
function cancelEdit() {
if (confirm("編集をキャンセルして一覧画面に戻りますか?")) {
localStorage.removeItem("editSource");
window.location.href = "journal-list.html";
}
}
</script>
</body>
</html>