197 lines
5.1 KiB
HTML
197 lines
5.1 KiB
HTML
<!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">
|
||
|
||
<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>
|
||
|
||
<button onclick="addRow()">+ 行追加</button>
|
||
<button onclick="submitEntry()">修正版仕訳を登録</button>
|
||
|
||
<div id="result"></div>
|
||
|
||
<script>
|
||
const API = "http://127.0.0.1:18080";
|
||
let accounts = [];
|
||
|
||
// ------------------------------------
|
||
// 初期化
|
||
// ------------------------------------
|
||
async function init() {
|
||
// 科目取得
|
||
const res = await fetch(`${API}/accounts`);
|
||
accounts = await res.json();
|
||
|
||
// 元仕訳取得
|
||
const src = localStorage.getItem("editSource");
|
||
if (!src) {
|
||
showError("修正元の仕訳データが見つかりません");
|
||
return;
|
||
}
|
||
const data = JSON.parse(src);
|
||
|
||
// ヘッダ反映
|
||
document.getElementById("entryDate").value = data.entry_date;
|
||
document.getElementById("description").value = `【修正後】${data.description}`;
|
||
|
||
// 明細反映
|
||
data.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 || "";
|
||
}
|
||
}
|
||
|
||
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("借方合計と貸方合計が一致していません");
|
||
|
||
const payload = {
|
||
entry_date: entryDate,
|
||
description: desc,
|
||
lines
|
||
};
|
||
|
||
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";
|
||
}
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|