desktop修正
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
body {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont;
|
||||
margin: 20px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-top: 10px;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #999;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
a {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,35 @@
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>NJTS 会計システム</title>
|
||||
<title>試算表</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>NJTS 会計システム</h1>
|
||||
<h1>試算表</h1>
|
||||
|
||||
<ul>
|
||||
<li><a href="accounts.html">科目マスタ</a></li>
|
||||
<li><a href="trial-balance.html">試算表</a></li>
|
||||
</ul>
|
||||
<label>
|
||||
期間:
|
||||
<input type="date" id="from" />
|
||||
〜
|
||||
<input type="date" id="to" />
|
||||
</label>
|
||||
<button onclick="loadTrialBalance()">表示</button>
|
||||
|
||||
<table id="trial-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科目コード</th>
|
||||
<th>科目名</th>
|
||||
<th>期首</th>
|
||||
<th>借方</th>
|
||||
<th>贷方</th>
|
||||
<th>期末</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/trial_balance.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
47
frontend/journal.html
Normal file
47
frontend/journal.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>仕訳入力</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>仕訳入力</h1>
|
||||
|
||||
<label>
|
||||
日付:
|
||||
<input type="date" id="journal-date" />
|
||||
</label>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<label>
|
||||
摘要:
|
||||
<input type="text" id="description" size="40" />
|
||||
</label>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<table id="lines-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科目ID</th>
|
||||
<th>借方</th>
|
||||
<th>贷方</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<button onclick="addLine()">+ 行追加</button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<button onclick="submitJournal()">保存</button>
|
||||
<a href="index.html">← 戻る</a>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/journal.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,10 +1,13 @@
|
||||
const API_BASE = "http://localhost:18080";
|
||||
const API_BASE = "http://127.0.0.1:18080";
|
||||
|
||||
async function apiGet(path) {
|
||||
const res = await fetch(`${API_BASE}${path}`);
|
||||
async function apiGet(path, params = {}) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
const url = `${API_BASE}${path}?${query}`;
|
||||
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(text || "APIエラー");
|
||||
const data = await res.json();
|
||||
throw new Error(data.detail || "APIエラー");
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
|
||||
65
frontend/js/journal.js
Normal file
65
frontend/js/journal.js
Normal file
@@ -0,0 +1,65 @@
|
||||
function addLine() {
|
||||
const tbody = document.querySelector("#lines-table tbody");
|
||||
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td><input type="number" class="account-id" /></td>
|
||||
<td><input type="number" class="debit" value="0" /></td>
|
||||
<td><input type="number" class="credit" value="0" /></td>
|
||||
<td><button onclick="this.closest('tr').remove()">削除</button></td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
async function submitJournal() {
|
||||
const date = document.getElementById("journal-date").value;
|
||||
const desc = document.getElementById("description").value;
|
||||
|
||||
if (!date) {
|
||||
alert("日付を入力してください");
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
document.querySelectorAll("#lines-table tbody tr").forEach((tr) => {
|
||||
const accountId = tr.querySelector(".account-id").value;
|
||||
const debit = tr.querySelector(".debit").value || 0;
|
||||
const credit = tr.querySelector(".credit").value || 0;
|
||||
|
||||
if (!accountId) return;
|
||||
|
||||
lines.push({
|
||||
account_id: Number(accountId),
|
||||
debit: Number(debit),
|
||||
credit: Number(credit),
|
||||
});
|
||||
});
|
||||
|
||||
if (lines.length < 2) {
|
||||
alert("仕訳明細は2行以上必要です");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch("http://127.0.0.1:18080/journals", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
journal_date: date,
|
||||
description: desc,
|
||||
lines: lines,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
throw new Error(data.detail || "登録失敗");
|
||||
}
|
||||
});
|
||||
|
||||
alert("仕訳を登録しました");
|
||||
location.href = "index.html";
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
}
|
||||
37
frontend/js/ledger.js
Normal file
37
frontend/js/ledger.js
Normal file
@@ -0,0 +1,37 @@
|
||||
async function loadLedger() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
const accountId = params.get("account_id");
|
||||
const from = params.get("from");
|
||||
const to = params.get("to");
|
||||
|
||||
try {
|
||||
const data = await apiGet("/general-ledger", {
|
||||
account_id: accountId,
|
||||
date_from: from,
|
||||
date_to: to,
|
||||
});
|
||||
|
||||
document.getElementById(
|
||||
"account-title"
|
||||
).innerText = `${data.account.account_code} ${data.account.account_name}`;
|
||||
|
||||
const tbody = document.getElementById("ledger-body");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
data.lines.forEach((line) => {
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${line.journal_date}</td>
|
||||
<td>${line.description}</td>
|
||||
<td>${line.debit}</td>
|
||||
<td>${line.credit}</td>
|
||||
<td>${line.balance}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
loadLedger();
|
||||
152
frontend/js/opening_balance.js
Normal file
152
frontend/js/opening_balance.js
Normal file
@@ -0,0 +1,152 @@
|
||||
let accounts = [];
|
||||
let retainedEarnings = null;
|
||||
let isLocked = false;
|
||||
|
||||
function formatNumber(n) {
|
||||
return Number(n || 0).toLocaleString();
|
||||
}
|
||||
|
||||
function recalcTotals() {
|
||||
let debit = 0;
|
||||
let credit = 0;
|
||||
|
||||
// 先算:不含繰越利益
|
||||
accounts.forEach((a) => {
|
||||
if (a.account_name === "繰越利益") return;
|
||||
debit += Number(a.opening_debit || 0);
|
||||
credit += Number(a.opening_credit || 0);
|
||||
});
|
||||
|
||||
const diff = debit - credit;
|
||||
|
||||
// 自动计算 繰越利益
|
||||
if (retainedEarnings) {
|
||||
if (diff > 0) {
|
||||
retainedEarnings.opening_debit = 0;
|
||||
retainedEarnings.opening_credit = diff;
|
||||
} else {
|
||||
retainedEarnings.opening_debit = -diff;
|
||||
retainedEarnings.opening_credit = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 再算一次(包含繰越利益)
|
||||
let finalDebit = 0;
|
||||
let finalCredit = 0;
|
||||
|
||||
accounts.forEach((a) => {
|
||||
finalDebit += Number(a.opening_debit || 0);
|
||||
finalCredit += Number(a.opening_credit || 0);
|
||||
});
|
||||
|
||||
document.getElementById("totalDebit").innerText = formatNumber(finalDebit);
|
||||
document.getElementById("totalCredit").innerText = formatNumber(finalCredit);
|
||||
document.getElementById("diff").innerText = formatNumber(
|
||||
finalDebit - finalCredit
|
||||
);
|
||||
}
|
||||
|
||||
async function loadOpeningBalances() {
|
||||
const year = document.getElementById("fiscalYear").value;
|
||||
|
||||
const data = await apiGet("/opening-balances", { fiscal_year: year });
|
||||
|
||||
accounts = data;
|
||||
retainedEarnings = accounts.find((a) => a.account_name === "繰越利益");
|
||||
|
||||
const tbody = document.querySelector("#balanceTable tbody");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
GROUPS.forEach((group) => {
|
||||
// 分组标题行
|
||||
const headerTr = document.createElement("tr");
|
||||
headerTr.innerHTML = `
|
||||
<td colspan="4" style="background:#ddd; font-weight:bold;">
|
||||
${group.label}
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(headerTr);
|
||||
|
||||
accounts.forEach((a, i) => {
|
||||
if (a.account_type !== group.key) return;
|
||||
|
||||
const isRetained = a.account_name === "繰越利益";
|
||||
const tr = document.createElement("tr");
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>${a.account_code}</td>
|
||||
<td>${a.account_name}</td>
|
||||
<td>
|
||||
<input type="number" value="${a.opening_debit}"
|
||||
${isRetained ? "readonly" : ""}
|
||||
onchange="accounts[${i}].opening_debit=this.value; recalcTotals();">
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" value="${a.opening_credit}"
|
||||
${isRetained ? "readonly" : ""}
|
||||
onchange="accounts[${i}].opening_credit=this.value; recalcTotals();">
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
});
|
||||
|
||||
// 年度锁定チェック
|
||||
fetch(
|
||||
`http://127.0.0.1:18080/opening-balances/lock-status?fiscal_year=${year}`
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
isLocked = data.is_locked;
|
||||
document.querySelector(
|
||||
"button[onclick='saveOpeningBalances()']"
|
||||
).disabled = isLocked;
|
||||
if (isLocked) {
|
||||
alert("この会計年度の期首残高は確定されています。");
|
||||
}
|
||||
});
|
||||
|
||||
recalcTotals();
|
||||
}
|
||||
|
||||
async function saveOpeningBalances() {
|
||||
if (isLocked) {
|
||||
alert("この会計年度の期首残高は確定されているため、保存できません。");
|
||||
return;
|
||||
}
|
||||
const year = Number(document.getElementById("fiscalYear").value);
|
||||
|
||||
const balances = accounts
|
||||
// ❗ 排除 繰越利益
|
||||
.filter(
|
||||
(a) =>
|
||||
a.account_name !== "繰越利益" &&
|
||||
(Number(a.opening_debit) !== 0 || Number(a.opening_credit) !== 0)
|
||||
)
|
||||
.map((a) => ({
|
||||
account_id: a.account_id,
|
||||
debit: Number(a.opening_debit || 0),
|
||||
credit: Number(a.opening_credit || 0),
|
||||
}));
|
||||
|
||||
try {
|
||||
await fetch("http://127.0.0.1:18080/opening-balances", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
fiscal_year: year,
|
||||
balances: balances,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
throw new Error(data.detail);
|
||||
}
|
||||
});
|
||||
|
||||
alert("期首残高を保存しました");
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
document.getElementById("load").onclick = async () => {
|
||||
async function loadTrialBalance() {
|
||||
const from = document.getElementById("from").value;
|
||||
const to = document.getElementById("to").value;
|
||||
|
||||
@@ -8,22 +8,33 @@ document.getElementById("load").onclick = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await apiGet(`/trial-balance?date_from=${from}&date_to=${to}`);
|
||||
const tbody = document.getElementById("tb-body");
|
||||
const data = await apiGet("/trial-balance", {
|
||||
date_from: from,
|
||||
date_to: to,
|
||||
});
|
||||
|
||||
const tbody = document.querySelector("#trial-table tbody");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
data.accounts.forEach((a) => {
|
||||
data.accounts.forEach((acc) => {
|
||||
const tr = document.createElement("tr");
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>${a.account_name}</td>
|
||||
<td style="text-align:right">${a.opening_balance}</td>
|
||||
<td style="text-align:right">${a.debit}</td>
|
||||
<td style="text-align:right">${a.credit}</td>
|
||||
<td style="text-align:right">${a.closing_balance}</td>
|
||||
<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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
29
frontend/ledger.html
Normal file
29
frontend/ledger.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>元帳</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="account-title">元帳</h1>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日付</th>
|
||||
<th>摘要</th>
|
||||
<th>借方</th>
|
||||
<th>贷方</th>
|
||||
<th>残高</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ledger-body"></tbody>
|
||||
</table>
|
||||
|
||||
<a href="index.html">← 試算表へ戻る</a>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/ledger.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
48
frontend/opening_balance.html
Normal file
48
frontend/opening_balance.html
Normal file
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>期首残高入力</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>期首残高入力</h1>
|
||||
|
||||
<label>
|
||||
会計年度:
|
||||
<input type="number" id="fiscalYear" value="2025" />
|
||||
</label>
|
||||
<button onclick="loadOpeningBalances()">読込</button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<table id="balanceTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>科目コード</th>
|
||||
<th>科目名</th>
|
||||
<th>借方期首</th>
|
||||
<th>贷方期首</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<strong>借方合計:</strong><span id="totalDebit">0</span><br />
|
||||
<strong>贷方合計:</strong><span id="totalCredit">0</span><br />
|
||||
<strong>差額:</strong><span id="diff">0</span>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<button onclick="saveOpeningBalances()">保存</button>
|
||||
<a href="index.html">← 戻る</a>
|
||||
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/opening_balance.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
s
|
||||
Reference in New Issue
Block a user