修正
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
|
||||
<div class="row">
|
||||
<label>仕訳日</label>
|
||||
<input type="date" id="entryDate" />
|
||||
<input type="date" id="entryDate" onchange="onEntryDateChanged()"/>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
@@ -37,6 +37,14 @@
|
||||
<label>仮受消費税等 勘定</label>
|
||||
<select id="taxReceivedSelect"></select>
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<label>消費税 端数処理:</label>
|
||||
<select id="roundingMode">
|
||||
<option value="round">四捨五入</option>
|
||||
<option value="floor">切捨て</option>
|
||||
<option value="ceil">切上げ</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<table id="linesTable">
|
||||
<thead>
|
||||
@@ -57,7 +65,43 @@
|
||||
<button onclick="submitJournal()">登録</button>
|
||||
</div>
|
||||
|
||||
<h3>月次锁定管理</h3>
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
<label>年度:</label>
|
||||
<input type="number" id="lockYear" value="2025" style="width:80px;">
|
||||
|
||||
<label>月份:</label>
|
||||
<input type="number" id="lockMonth" min="1" max="12" value="6" style="width:60px;">
|
||||
</div>
|
||||
|
||||
<button onclick="lockMonth()">🔒 锁定</button>
|
||||
<button onclick="unlockMonth()">🔓 解锁</button>
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="lockResult" style="margin-top:10px;color:#333;"></div>
|
||||
|
||||
<div style="margin-top:12px;">
|
||||
<button onclick="refreshMonthLocks()">🔄 刷新锁定列表</button>
|
||||
<pre id="lockList" style="background:#f7f7f7;padding:8px;"></pre>
|
||||
</div>
|
||||
|
||||
<div id="monthLockBanner"
|
||||
style="display:none;
|
||||
margin:8px 0;
|
||||
padding:6px 10px;
|
||||
background:#ffecec;
|
||||
border:1px solid #ffaaaa;
|
||||
color:#a00000;">
|
||||
🔒 该月份已锁定,不能编辑仕訳
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let rowSeq = 0; // 各行唯一ID
|
||||
|
||||
const API = "http://127.0.0.1:18080";
|
||||
|
||||
let accounts = [];
|
||||
@@ -115,10 +159,16 @@ function setupTaxSelectors() {
|
||||
// -----------------------------
|
||||
// 行追加
|
||||
// -----------------------------
|
||||
function addRow(isTaxRow = false, taxInfo = null) {
|
||||
function addRow(isTaxRow = false, taxInfo = null, parentId = null) {
|
||||
const tbody = document.querySelector("#linesTable tbody");
|
||||
const tr = document.createElement("tr");
|
||||
if (isTaxRow) tr.classList.add("tax-row");
|
||||
|
||||
if (!isTaxRow) {
|
||||
tr.dataset.rowId = String(++rowSeq);
|
||||
} else {
|
||||
tr.classList.add("tax-row");
|
||||
tr.dataset.parentId = parentId;
|
||||
}
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>
|
||||
@@ -149,26 +199,46 @@ function addRow(isTaxRow = false, taxInfo = null) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 普通行:绑定事件
|
||||
if (!isTaxRow) {
|
||||
const recalc = () => handleTax(tr);
|
||||
tr.querySelector(".debit").addEventListener("input", recalc);
|
||||
tr.querySelector(".credit").addEventListener("input", recalc);
|
||||
tr.querySelector(".taxType").addEventListener("change", recalc);
|
||||
tr.querySelector(".taxDirection").addEventListener("change", recalc);
|
||||
}
|
||||
|
||||
return tr;
|
||||
}
|
||||
|
||||
// 删除该行的所有税行
|
||||
function removeTaxRowsOf(row) {
|
||||
const id = row.dataset.rowId;
|
||||
if (!id) return;
|
||||
document.querySelectorAll(`tr.tax-row[data-parent-id="${id}"]`).forEach(r => r.remove());
|
||||
}
|
||||
|
||||
function removeRow(btn) {
|
||||
const row = btn.closest("tr");
|
||||
removeTaxRowsOf(row);
|
||||
row.remove();
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------
|
||||
// 税行 自動生成/更新(10%/8%のみ、四捨五入)
|
||||
// -----------------------------
|
||||
function handleTax(row) {
|
||||
removeTaxRow(row);
|
||||
// 清理旧税行
|
||||
removeTaxRowsOf(row);
|
||||
|
||||
const taxType = row.querySelector(".taxType").value;
|
||||
const taxDir = row.querySelector(".taxDirection").value;
|
||||
@@ -176,28 +246,48 @@ function handleTax(row) {
|
||||
const credit = Number(row.querySelector(".credit").value || 0);
|
||||
|
||||
if (taxType === "none" || taxDir === "none") return;
|
||||
const baseAmount = debit || credit;
|
||||
if (!baseAmount) return;
|
||||
|
||||
const totalAmount = debit || credit;
|
||||
if (!totalAmount) return;
|
||||
|
||||
const rate = taxType === "8" ? 0.08 : 0.10;
|
||||
const taxAmount = Math.round(baseAmount * rate);
|
||||
const roundingMode = document.getElementById("roundingMode").value;
|
||||
|
||||
// 税込 → 税額逆算
|
||||
const rawTax = totalAmount * rate / (1 + rate);
|
||||
let taxAmount;
|
||||
switch (roundingMode) {
|
||||
case "floor": taxAmount = Math.floor(rawTax); break;
|
||||
case "ceil": taxAmount = Math.ceil(rawTax); break;
|
||||
default: taxAmount = Math.round(rawTax);
|
||||
}
|
||||
|
||||
const parentId = row.dataset.rowId;
|
||||
|
||||
if (taxDir === "paid") {
|
||||
const taxAcc = document.getElementById("taxPaidSelect").value;
|
||||
if (!taxAcc) { alert("仮払消費税等 勘定を選択してください"); return; }
|
||||
addRow(true, { accountId: taxAcc, amount: taxAmount, side: "debit" });
|
||||
if (!taxAcc) return alert("仮払消費税等 勘定を選択してください");
|
||||
addRow(true, { accountId: taxAcc, amount: taxAmount, side: "debit" }, parentId);
|
||||
}
|
||||
if (taxDir === "received") {
|
||||
const taxAcc = document.getElementById("taxReceivedSelect").value;
|
||||
if (!taxAcc) { alert("仮受消費税等 勘定を選択してください"); return; }
|
||||
addRow(true, { accountId: taxAcc, amount: taxAmount, side: "credit" });
|
||||
if (!taxAcc) return alert("仮受消費税等 勘定を選択してください");
|
||||
addRow(true, { accountId: taxAcc, amount: taxAmount, side: "credit" }, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 税行は常に「直後の1行」を使う
|
||||
function removeTaxRow(row) {
|
||||
const next = row.nextElementSibling;
|
||||
if (next && next.classList.contains("tax-row")) next.remove();
|
||||
const id = row.dataset.rowId;
|
||||
if (!id) return;
|
||||
|
||||
document
|
||||
.querySelectorAll(`tr.tax-row[data-parent-id="${id}"]`)
|
||||
.forEach(tr => tr.remove());
|
||||
}
|
||||
|
||||
function removeRow(btn) {
|
||||
@@ -254,6 +344,137 @@ async function submitJournal() {
|
||||
alert(`登録完了(ID=${result.journal_entry_id})`);
|
||||
location.reload();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const API_BASE = "http://127.0.0.1:18080";
|
||||
|
||||
async function callApi(path) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}${path}`, { method: "POST" });
|
||||
const text = await res.text();
|
||||
let data = {};
|
||||
try { data = text ? JSON.parse(text) : {}; } catch { data = { detail: text }; }
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.detail || `HTTP ${res.status}`);
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw new Error(e.message || "请求失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function lockMonth() {
|
||||
const year = document.getElementById("lockYear").value;
|
||||
const month = document.getElementById("lockMonth").value;
|
||||
|
||||
const el = document.getElementById("lockResult");
|
||||
el.innerText = "处理中...";
|
||||
|
||||
try {
|
||||
await callApi(`/month-locks/lock?fiscal_year=${year}&month=${month}`);
|
||||
el.innerText = `✅ ${year}年${month}月 已锁定`;
|
||||
await refreshMonthLocks(); // 可选:自动刷新列表
|
||||
} catch (e) {
|
||||
el.innerText = `❌ 错误:${e.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
async function unlockMonth() {
|
||||
const year = document.getElementById("lockYear").value;
|
||||
const month = document.getElementById("lockMonth").value;
|
||||
|
||||
const el = document.getElementById("lockResult");
|
||||
el.innerText = "处理中...";
|
||||
|
||||
try {
|
||||
await callApi(`/month-locks/unlock?fiscal_year=${year}&month=${month}`);
|
||||
el.innerText = `✅ ${year}年${month}月 已解锁`;
|
||||
await refreshMonthLocks(); // 可选:自动刷新列表
|
||||
} catch (e) {
|
||||
el.innerText = `❌ 错误:${e.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function refreshMonthLocks() {
|
||||
const el = document.getElementById("lockList");
|
||||
el.textContent = "读取中...";
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/month-locks`);
|
||||
const data = await res.json();
|
||||
|
||||
// 只显示 locked 的,避免太长
|
||||
const locked = (data || []).filter(x => x.is_locked);
|
||||
|
||||
if (locked.length === 0) {
|
||||
el.textContent = "(当前没有锁定月份)";
|
||||
return;
|
||||
}
|
||||
|
||||
el.textContent = locked
|
||||
.map(x => `${x.fiscal_year}-${String(x.month).padStart(2, "0")} locked_by=${x.locked_by ?? ""}`)
|
||||
.join("\n");
|
||||
} catch (e) {
|
||||
el.textContent = `读取失败:${e.message || e}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function checkMonthLockByDate(dateStr) {
|
||||
if (!dateStr) return false;
|
||||
|
||||
const d = new Date(dateStr);
|
||||
const year = d.getFullYear();
|
||||
const month = d.getMonth() + 1;
|
||||
|
||||
const res = await fetch(`${API_BASE}/month-locks`);
|
||||
const data = await res.json();
|
||||
|
||||
return (data || []).some(
|
||||
x => x.fiscal_year === year && x.month === month && x.is_locked
|
||||
);
|
||||
}
|
||||
|
||||
async function onEntryDateChanged() {
|
||||
const dateStr = document.getElementById("entryDate").value;
|
||||
const locked = await checkMonthLockByDate(dateStr);
|
||||
|
||||
const banner = document.getElementById("monthLockBanner");
|
||||
|
||||
if (locked) {
|
||||
banner.style.display = "block";
|
||||
setJournalReadonly(true);
|
||||
} else {
|
||||
banner.style.display = "none";
|
||||
setJournalReadonly(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setJournalReadonly(readonly) {
|
||||
// 保存按钮
|
||||
const saveBtn = document.getElementById("saveButton");
|
||||
if (saveBtn) saveBtn.disabled = readonly;
|
||||
|
||||
// 行追加按钮
|
||||
const addRowBtn = document.getElementById("addRowButton");
|
||||
if (addRowBtn) addRowBtn.disabled = readonly;
|
||||
|
||||
// 所有“删除行”按钮
|
||||
document.querySelectorAll(".delete-row-btn").forEach(btn => {
|
||||
btn.disabled = readonly;
|
||||
});
|
||||
|
||||
// 所有仕訳输入项(金额 / 科目等)
|
||||
document.querySelectorAll(".journal-input").forEach(input => {
|
||||
input.disabled = readonly;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -1,40 +1,32 @@
|
||||
async function loadTrialBalance() {
|
||||
const from = document.getElementById("from").value;
|
||||
const to = document.getElementById("to").value;
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
console.log("trial-balance.js 已执行");
|
||||
const API_URL = "http://127.0.0.1:18080/trial-balance";
|
||||
|
||||
if (!from || !to) {
|
||||
alert("期間を指定してください");
|
||||
return;
|
||||
}
|
||||
fetch(API_URL)
|
||||
.then(r => { if (!r.ok) throw new Error("API 返回错误: " + r.status); return r.json(); })
|
||||
.then(data => {
|
||||
|
||||
try {
|
||||
const data = await apiGet("/trial-balance", {
|
||||
date_from: from,
|
||||
date_to: to,
|
||||
});
|
||||
console.log("试算表 API 数据:", data);
|
||||
const tbody = document.querySelector("#trialBalanceTable tbody");
|
||||
if (!tbody) { console.error("未找到 #trialBalanceTable tbody"); return; }
|
||||
|
||||
const tbody = document.querySelector("#trial-table tbody");
|
||||
tbody.innerHTML = "";
|
||||
let totalDebit = 0, totalCredit = 0;
|
||||
data.accounts.forEach(row => {
|
||||
const debit = Number(row.debit ?? 0);
|
||||
const credit = Number(row.credit ?? 0);
|
||||
totalDebit += debit; totalCredit += credit;
|
||||
|
||||
data.accounts.forEach((acc) => {
|
||||
const tr = document.createElement("tr");
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td class="left">${row.account_code}</td>
|
||||
<td class="left">${row.account_name}</td>
|
||||
<td>${debit.toLocaleString()}</td>
|
||||
<td>${credit.toLocaleString()}</td>`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>${acc.account_code}</td>
|
||||
<td>
|
||||
<a href="ledger.html?account_id=${acc.account_id}&from=${from}&to=${to}">
|
||||
${acc.account_name}
|
||||
</a>
|
||||
</td>
|
||||
<td>${acc.opening_balance}</td>
|
||||
<td>${acc.debit}</td>
|
||||
<td>${acc.credit}</td>
|
||||
<td>${acc.closing_balance}</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
}
|
||||
document.getElementById("totalDebit").textContent = totalDebit.toLocaleString();
|
||||
document.getElementById("totalCredit").textContent = totalCredit.toLocaleString();
|
||||
})
|
||||
.catch(err => { console.error("試算表读取失败:", err); alert("試算表读取失败,请查看控制台日志"); });
|
||||
});
|
||||
|
||||
@@ -1,37 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>試算表</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>試算表</h1>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>试算表</title>
|
||||
<style>
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 6px 10px;
|
||||
text-align: right;
|
||||
}
|
||||
th {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
td.left {
|
||||
text-align: left;
|
||||
}
|
||||
tfoot td {
|
||||
font-weight: bold;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<label>
|
||||
期首:
|
||||
<input type="date" id="from" />
|
||||
</label>
|
||||
<label>
|
||||
期末:
|
||||
<input type="date" id="to" />
|
||||
</label>
|
||||
<button id="load">表示</button>
|
||||
<h2>試算表</h2>
|
||||
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科目</th>
|
||||
<th>期首</th>
|
||||
<th>借方</th>
|
||||
<th>貸方</th>
|
||||
<th>期末</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tb-body"></tbody>
|
||||
</table>
|
||||
<table id="trialBalanceTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科目代码</th>
|
||||
<th>科目名称</th>
|
||||
<th>借方合计</th>
|
||||
<th>贷方合计</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- JS 动态插入 -->
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2">合计</td>
|
||||
<td id="totalDebit">0</td>
|
||||
<td id="totalCredit">0</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/trial-balance.js"></script>
|
||||
</body>
|
||||
<script src="./js/trial-balance.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user