修正
This commit is contained in:
@@ -1,98 +1,134 @@
|
||||
<!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>
|
||||
<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>
|
||||
|
||||
<h2>修正仕訳入力</h2>
|
||||
<label>仕訳日</label>
|
||||
<input type="date" id="entryDate" />
|
||||
|
||||
<label>仕訳日</label>
|
||||
<input type="date" id="entryDate">
|
||||
<label>摘要</label>
|
||||
<input type="text" id="description" style="width: 60%" />
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<button onclick="addRow()">+ 行追加</button>
|
||||
<button onclick="submitEntry()">修正版仕訳を登録</button>
|
||||
<div id="result"></div>
|
||||
|
||||
<div id="result"></div>
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
let accounts = [];
|
||||
|
||||
<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;
|
||||
|
||||
// ------------------------------------
|
||||
// 初期化
|
||||
// ------------------------------------
|
||||
async function init() {
|
||||
// 科目取得
|
||||
const res = await fetch(`${API}/accounts`);
|
||||
accounts = await res.json();
|
||||
// 元仕訳取得
|
||||
const src = localStorage.getItem("editSource");
|
||||
if (!src) {
|
||||
showError("修正元の仕訳データが見つかりません");
|
||||
return;
|
||||
}
|
||||
const srcData = JSON.parse(src);
|
||||
|
||||
// 元仕訳取得
|
||||
const src = localStorage.getItem("editSource");
|
||||
if (!src) {
|
||||
showError("修正元の仕訳データが見つかりません");
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(src);
|
||||
// ヘッダ反映
|
||||
document.getElementById("entryDate").value = srcData.entry_date;
|
||||
document.getElementById(
|
||||
"description"
|
||||
).value = `【修正後】${srcData.description}`;
|
||||
|
||||
// ヘッダ反映
|
||||
document.getElementById("entryDate").value = data.entry_date;
|
||||
document.getElementById("description").value = `【修正後】${data.description}`;
|
||||
// 明細反映
|
||||
srcData.lines.forEach((l) => addRow(l));
|
||||
|
||||
// 明細反映
|
||||
data.lines.forEach(l => addRow(l));
|
||||
recalc();
|
||||
}
|
||||
init();
|
||||
|
||||
recalc();
|
||||
}
|
||||
init();
|
||||
// ------------------------------------
|
||||
// 行操作
|
||||
// ------------------------------------
|
||||
function addRow(line = null) {
|
||||
const tbody = document.querySelector("#linesTable tbody");
|
||||
const tr = document.createElement("tr");
|
||||
|
||||
// ------------------------------------
|
||||
// 行操作
|
||||
// ------------------------------------
|
||||
function addRow(line = null) {
|
||||
const tbody = document.querySelector("#linesTable tbody");
|
||||
const tr = document.createElement("tr");
|
||||
|
||||
tr.innerHTML = `
|
||||
tr.innerHTML = `
|
||||
<td>
|
||||
<select>
|
||||
${accounts.map(a =>
|
||||
`<option value="${a.account_id}">${a.account_code} ${a.account_name}</option>`
|
||||
).join("")}
|
||||
${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>
|
||||
@@ -100,97 +136,108 @@ function addRow(line = null) {
|
||||
<td><button onclick="removeRow(this)">削除</button></td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(tr);
|
||||
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 || "";
|
||||
}
|
||||
}
|
||||
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 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();
|
||||
}
|
||||
// ------------------------------------
|
||||
// 合計再計算
|
||||
// ------------------------------------
|
||||
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 = "";
|
||||
// ------------------------------------
|
||||
// 登録
|
||||
// ------------------------------------
|
||||
async function submitEntry() {
|
||||
const result = document.getElementById("result");
|
||||
result.textContent = "";
|
||||
result.className = "";
|
||||
|
||||
const entryDate = document.getElementById("entryDate").value;
|
||||
const desc = document.getElementById("description").value;
|
||||
const entryDate = document.getElementById("entryDate").value;
|
||||
const desc = document.getElementById("description").value;
|
||||
|
||||
if (!entryDate || !desc) {
|
||||
showError("日付と摘要は必須です");
|
||||
return;
|
||||
}
|
||||
if (!entryDate || !desc) {
|
||||
showError("日付と摘要は必須です");
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
let d = 0, c = 0;
|
||||
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;
|
||||
});
|
||||
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("借方合計と貸方合計が一致していません");
|
||||
if (lines.length < 2) return showError("仕訳行は2行以上必要です");
|
||||
if (d !== c) return showError("借方合計と貸方合計が一致していません");
|
||||
|
||||
const payload = {
|
||||
entry_date: entryDate,
|
||||
description: desc,
|
||||
lines
|
||||
};
|
||||
const payload = {
|
||||
entry_date: entryDate,
|
||||
description: desc,
|
||||
lines,
|
||||
tax_paid_account_id: null,
|
||||
tax_received_account_id: null,
|
||||
rounding_mode: "floor",
|
||||
};
|
||||
|
||||
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("通信エラー");
|
||||
}
|
||||
}
|
||||
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>
|
||||
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";
|
||||
}
|
||||
|
||||
</body>
|
||||
function cancelEdit() {
|
||||
if (confirm("編集をキャンセルして一覧画面に戻りますか?")) {
|
||||
localStorage.removeItem("editSource");
|
||||
window.location.href = "journal-list.html";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
<div style="margin-bottom: 10px">
|
||||
<label>消費税 端数処理:</label>
|
||||
<select id="roundingMode">
|
||||
<option value="round">四捨五入</option>
|
||||
<option value="floor">切捨て</option>
|
||||
<option value="round">四捨五入</option>
|
||||
<option value="ceil">切上げ</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -104,7 +104,14 @@
|
||||
|
||||
<div style="margin-bottom: 8px">
|
||||
<label>年度:</label>
|
||||
<input type="number" id="lockYear" value="2025" style="width: 80px" />
|
||||
<input
|
||||
type="number"
|
||||
id="lockYear"
|
||||
value="2025"
|
||||
min="1900"
|
||||
max="2999"
|
||||
style="width: 80px"
|
||||
/>
|
||||
|
||||
<label>月份:</label>
|
||||
<input
|
||||
@@ -353,8 +360,8 @@
|
||||
const rate = taxType === "8" ? 0.08 : 0.1;
|
||||
const roundingMode = document.getElementById("roundingMode").value;
|
||||
|
||||
// 税込 → 税額逆算
|
||||
const rawTax = (totalAmount * rate) / (1 + rate);
|
||||
// 税抜額 × 税率 = 税額
|
||||
const rawTax = totalAmount * rate;
|
||||
let taxAmount;
|
||||
switch (roundingMode) {
|
||||
case "floor":
|
||||
@@ -406,7 +413,7 @@
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// 登録(最小可用版)
|
||||
// 登録(税行自動生成版)
|
||||
// -----------------------------
|
||||
async function submitJournal() {
|
||||
const entryDate = document.getElementById("entryDate").value;
|
||||
@@ -417,45 +424,55 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 明細収集
|
||||
// 明細収集(税情報も含む)
|
||||
const rows = document.querySelectorAll("#linesTable tbody tr");
|
||||
const lines = [];
|
||||
let totalDebit = 0,
|
||||
totalCredit = 0;
|
||||
|
||||
rows.forEach((row) => {
|
||||
if (row.classList.contains("tax-row")) return;
|
||||
if (row.classList.contains("tax-row")) return; // 税行は無視
|
||||
|
||||
const accountId = Number(row.querySelector(".account").value);
|
||||
const debit = Number(row.querySelector(".debit").value || 0);
|
||||
const credit = Number(row.querySelector(".credit").value || 0);
|
||||
const taxType = row.querySelector(".taxType").value;
|
||||
const taxDirection = row.querySelector(".taxDirection").value;
|
||||
|
||||
if (debit > 0 && credit > 0) {
|
||||
alert("同一行不能同时填写借方和贷方");
|
||||
throw new Error("invalid line");
|
||||
}
|
||||
if (debit === 0 && credit === 0) return;
|
||||
|
||||
lines.push({ account_id: accountId, debit: debit, credit: credit });
|
||||
totalDebit += debit;
|
||||
totalCredit += credit;
|
||||
const lineData = {
|
||||
account_id: accountId,
|
||||
debit: debit,
|
||||
credit: credit,
|
||||
};
|
||||
|
||||
// 税情報を追加
|
||||
if (taxType !== "none" && taxDirection !== "none") {
|
||||
lineData.tax_rate = parseInt(taxType);
|
||||
lineData.tax_direction = taxDirection;
|
||||
}
|
||||
|
||||
lines.push(lineData);
|
||||
});
|
||||
|
||||
if (lines.length === 0) {
|
||||
alert("仕訳明細がありません");
|
||||
return;
|
||||
}
|
||||
if (lines.length < 2) {
|
||||
alert("至少需要借方和贷方各一行");
|
||||
return;
|
||||
}
|
||||
// 借貸一致チェック(最小)
|
||||
if (totalDebit !== totalCredit) {
|
||||
alert(
|
||||
`借方(${totalDebit.toLocaleString()})と貸方(${totalCredit.toLocaleString()})が一致しません`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = { entry_date: entryDate, description, lines };
|
||||
const payload = {
|
||||
entry_date: entryDate,
|
||||
description,
|
||||
lines,
|
||||
tax_paid_account_id:
|
||||
Number(document.getElementById("taxPaidSelect").value) || null,
|
||||
tax_received_account_id:
|
||||
Number(document.getElementById("taxReceivedSelect").value) || null,
|
||||
rounding_mode: document.getElementById("roundingMode").value,
|
||||
};
|
||||
|
||||
const res = await fetch(`${API}/journal-entries`, {
|
||||
method: "POST",
|
||||
@@ -464,7 +481,11 @@
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
alert("登録に失敗しました");
|
||||
const errorData = await res.json().catch(() => ({}));
|
||||
const errorMsg =
|
||||
errorData.detail || `登録に失敗しました (HTTP ${res.status})`;
|
||||
console.error("Error:", errorData);
|
||||
alert(errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,108 +1,199 @@
|
||||
<!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; }
|
||||
th { background: #f5f5f5; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<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;
|
||||
}
|
||||
th {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>仕訳一覧</h2>
|
||||
|
||||
<h2>仕訳一覧</h2>
|
||||
<label>期間</label>
|
||||
<input type="date" id="fromDate" min="1900-01-01" max="2999-12-31" /> ~
|
||||
<input type="date" id="toDate" min="1900-01-01" max="2999-12-31" />
|
||||
|
||||
<label>期間</label>
|
||||
<input type="date" id="fromDate"> ~ <input type="date" id="toDate">
|
||||
<label>摘要</label>
|
||||
<input type="text" id="keyword" />
|
||||
|
||||
<label>摘要</label>
|
||||
<input type="text" id="keyword">
|
||||
<button onclick="search()">検索</button>
|
||||
|
||||
<button onclick="search()">検索</button>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日付</th>
|
||||
<th>摘要</th>
|
||||
<th>借方合計</th>
|
||||
<th>貸方合計</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="list"></tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日付</th>
|
||||
<th>摘要</th>
|
||||
<th>借方合計</th>
|
||||
<th>貸方合計</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="list"></tbody>
|
||||
</table>
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
async function search() {
|
||||
const from = document.getElementById("fromDate").value;
|
||||
const to = document.getElementById("toDate").value;
|
||||
const key = document.getElementById("keyword").value;
|
||||
|
||||
async function search() {
|
||||
const from = document.getElementById("fromDate").value;
|
||||
const to = document.getElementById("toDate").value;
|
||||
const key = document.getElementById("keyword").value;
|
||||
// 検索条件を保存
|
||||
localStorage.setItem(
|
||||
"journalSearchConditions",
|
||||
JSON.stringify({
|
||||
fromDate: from,
|
||||
toDate: to,
|
||||
keyword: key,
|
||||
})
|
||||
);
|
||||
|
||||
const qs = new URLSearchParams();
|
||||
if (from) qs.append("from_date", from);
|
||||
if (to) qs.append("to_date", to);
|
||||
if (key) qs.append("keyword", key);
|
||||
const qs = new URLSearchParams();
|
||||
if (from) qs.append("from_date", from);
|
||||
if (to) qs.append("to_date", to);
|
||||
if (key) qs.append("keyword", key);
|
||||
|
||||
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||
const data = await res.json();
|
||||
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||
const data = await res.json();
|
||||
|
||||
const tbody = document.getElementById("list");
|
||||
tbody.innerHTML = "";
|
||||
const tbody = document.getElementById("list");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
data.forEach(e => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${e.entry_date}</td>
|
||||
<td>${e.description}</td>
|
||||
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
||||
<td>
|
||||
<button onclick="view(${e.journal_entry_id})">表示</button>
|
||||
<button onclick="reverseAndEdit(${e.journal_entry_id})">修正</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
data.forEach((e) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${e.entry_date}</td>
|
||||
<td>${e.description}</td>
|
||||
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
||||
<td>
|
||||
<button onclick="view(${e.journal_entry_id})">表示</button>
|
||||
<button onclick="reverseAndEdit(${e.journal_entry_id})">修正</button>
|
||||
<button onclick="deleteEntry(${
|
||||
e.journal_entry_id
|
||||
})" style="color: red;">削除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
function view(id) {
|
||||
window.open(`journal-view.html?id=${id}`, "_blank");
|
||||
}
|
||||
function view(id) {
|
||||
window.open(`journal-view.html?id=${id}`, "_blank");
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
// 修正仕訳:逆仕訳を作って修正画面へ遷移
|
||||
// --------------------------------------------------
|
||||
async function reverseAndEdit(id) {
|
||||
if (!confirm("この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?")) return;
|
||||
// --------------------------------------------------
|
||||
// 修正仕訳:逆仕訳を作って修正画面へ遷移
|
||||
// --------------------------------------------------
|
||||
async function reverseAndEdit(id) {
|
||||
if (
|
||||
!confirm(
|
||||
"この仕訳の逆仕訳を作成し、修正仕訳入力画面を開きます。よろしいですか?"
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
// ① 逆仕訳を作成
|
||||
const res = await fetch(`${API}/journal-entries/${id}/reverse`, {
|
||||
method: "POST"
|
||||
});
|
||||
try {
|
||||
// ① 逆仕訳を作成
|
||||
const res = await fetch(`${API}/journal-entries/${id}/reverse`, {
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
alert(data.detail || "逆仕訳の作成に失敗しました");
|
||||
return;
|
||||
}
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
alert(
|
||||
data.detail || `逆仕訳の作成に失敗しました (HTTP ${res.status})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
alert(`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`);
|
||||
const data = await res.json();
|
||||
alert(
|
||||
`逆仕訳を作成しました(ID: ${data.reversed_journal_entry_id})`
|
||||
);
|
||||
|
||||
// ② 元仕訳内容を取得して修正画面へ遷移
|
||||
const src = await fetch(`${API}/journal-entries/${id}`);
|
||||
const srcData = await src.json();
|
||||
// ② 元仕訳内容を取得して修正画面へ遷移
|
||||
const srcRes = await fetch(`${API}/journal-entries/${id}`);
|
||||
if (!srcRes.ok) {
|
||||
alert("元仕訳の取得に失敗しました");
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem("editSource", JSON.stringify(srcData));
|
||||
const srcData = await srcRes.json();
|
||||
localStorage.setItem("editSource", JSON.stringify(srcData));
|
||||
|
||||
window.open("journal-edit.html", "_blank");
|
||||
}
|
||||
// 修正画面を開く
|
||||
window.open("journal-edit.html", "_blank");
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert(`エラーが発生しました: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
// --------------------------------------------------
|
||||
// 仕訳削除
|
||||
// --------------------------------------------------
|
||||
async function deleteEntry(id) {
|
||||
const reason = prompt("削除理由を入力してください:");
|
||||
if (!reason || reason.trim() === "") {
|
||||
alert("削除理由の入力が必要です");
|
||||
return;
|
||||
}
|
||||
|
||||
</body>
|
||||
if (!confirm("この仕訳を削除してもよろしいですか?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch(`${API}/journal-entries/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ deleted_reason: reason }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
alert(data.detail || "削除に失敗しました");
|
||||
return;
|
||||
}
|
||||
|
||||
alert("削除しました");
|
||||
search(); // リストを再読み込み
|
||||
}
|
||||
|
||||
// ページロード時に前回の検索条件を復元
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const savedConditions = localStorage.getItem("journalSearchConditions");
|
||||
if (savedConditions) {
|
||||
const conditions = JSON.parse(savedConditions);
|
||||
if (conditions.fromDate)
|
||||
document.getElementById("fromDate").value = conditions.fromDate;
|
||||
if (conditions.toDate)
|
||||
document.getElementById("toDate").value = conditions.toDate;
|
||||
if (conditions.keyword)
|
||||
document.getElementById("keyword").value = conditions.keyword;
|
||||
|
||||
// 自動的に検索を実行
|
||||
search();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
137
frontend/journal-view.html
Normal file
137
frontend/journal-view.html
Normal file
@@ -0,0 +1,137 @@
|
||||
<!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: 8px;
|
||||
}
|
||||
th {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.info-row {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
min-width: 120px;
|
||||
display: inline-block;
|
||||
}
|
||||
.total-row {
|
||||
background: #f0f8ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>仕訳詳細</h2>
|
||||
|
||||
<div id="info"></div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科目コード</th>
|
||||
<th>科目名</th>
|
||||
<th>借方</th>
|
||||
<th>貸方</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="lines"></tbody>
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 16px">
|
||||
<button onclick="window.close()">閉じる</button>
|
||||
<button onclick="goBack()">一覧に戻る</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
|
||||
async function loadJournalEntry() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const id = params.get("id");
|
||||
|
||||
if (!id) {
|
||||
alert("仕訳IDが指定されていません");
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch(`${API}/journal-entries/${id}`);
|
||||
if (!res.ok) {
|
||||
alert("仕訳の取得に失敗しました");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// ヘッダー情報
|
||||
document.getElementById("info").innerHTML = `
|
||||
<div class="info-row">
|
||||
<span class="info-label">仕訳ID:</span> ${data.journal_entry_id}
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">仕訳日:</span> ${data.entry_date}
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">摘要:</span> ${data.description}
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">会計年度:</span> ${data.fiscal_year}
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 明細
|
||||
const tbody = document.getElementById("lines");
|
||||
let totalDebit = 0;
|
||||
let totalCredit = 0;
|
||||
|
||||
data.lines.forEach((line) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${line.account_code}</td>
|
||||
<td>${line.account_name}</td>
|
||||
<td style="text-align:right">${
|
||||
line.debit > 0 ? line.debit.toLocaleString() : ""
|
||||
}</td>
|
||||
<td style="text-align:right">${
|
||||
line.credit > 0 ? line.credit.toLocaleString() : ""
|
||||
}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
|
||||
totalDebit += parseFloat(line.debit);
|
||||
totalCredit += parseFloat(line.credit);
|
||||
});
|
||||
|
||||
// 合計行
|
||||
const totalRow = document.createElement("tr");
|
||||
totalRow.className = "total-row";
|
||||
totalRow.innerHTML = `
|
||||
<td colspan="2" style="text-align:center">合計</td>
|
||||
<td style="text-align:right">${totalDebit.toLocaleString()}</td>
|
||||
<td style="text-align:right">${totalCredit.toLocaleString()}</td>
|
||||
`;
|
||||
tbody.appendChild(totalRow);
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
window.location.href = "journal-list.html";
|
||||
}
|
||||
|
||||
loadJournalEntry();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user