修改
This commit is contained in:
@@ -45,6 +45,72 @@
|
||||
.tax-row {
|
||||
background: #f0f8ff;
|
||||
}
|
||||
.print-header {
|
||||
display: none;
|
||||
}
|
||||
.page-break {
|
||||
page-break-before: always;
|
||||
}
|
||||
.page-number {
|
||||
display: none;
|
||||
}
|
||||
@media print {
|
||||
@page {
|
||||
margin: 20mm;
|
||||
}
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
#printArea,
|
||||
#printArea * {
|
||||
visibility: visible;
|
||||
}
|
||||
#printArea {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
button,
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
.print-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9em;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.print-header-left {
|
||||
text-align: left;
|
||||
}
|
||||
.print-header-right {
|
||||
text-align: right;
|
||||
}
|
||||
.page-number {
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
#printArea table {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
#printArea th,
|
||||
#printArea td {
|
||||
padding: 3px 4px;
|
||||
}
|
||||
#printArea h3 {
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -217,21 +283,15 @@
|
||||
</label>
|
||||
|
||||
<button onclick="searchJournals()" style="margin-left: 8px">検索</button>
|
||||
<button
|
||||
onclick="printSearchResults()"
|
||||
style="margin-left: 8px; color: blue"
|
||||
>
|
||||
🖨️ 印刷
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日付</th>
|
||||
<th>摘要</th>
|
||||
<th>借方合計</th>
|
||||
<th>貸方合計</th>
|
||||
<th>税率</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="searchResultList"></tbody>
|
||||
</table>
|
||||
<div id="printArea"></div>
|
||||
|
||||
<script>
|
||||
let rowSeq = 0; // 各行唯一ID
|
||||
@@ -253,16 +313,26 @@
|
||||
|
||||
const ACCOUNT_GROUPS = [
|
||||
{
|
||||
label: "💰 資金",
|
||||
codes: ["101", "102", "103", "104"],
|
||||
label: "💰 現金・預金",
|
||||
codes: ["101", "102", "103", "104", "105"],
|
||||
},
|
||||
{
|
||||
label: "📦 流動資産",
|
||||
codes: ["201", "202", "203", "204", "205"],
|
||||
codes: ["201", "202", "203", "204", "205", "210"],
|
||||
},
|
||||
{
|
||||
label: "🏗 固定資産",
|
||||
codes: ["401", "402", "403", "404", "405", "406", "407", "408"],
|
||||
label: "🏗️ 固定資産",
|
||||
codes: [
|
||||
"301",
|
||||
"401",
|
||||
"402",
|
||||
"403",
|
||||
"404",
|
||||
"405",
|
||||
"406",
|
||||
"407",
|
||||
"408",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "💳 負債",
|
||||
@@ -276,16 +346,30 @@
|
||||
"507",
|
||||
"508",
|
||||
"509",
|
||||
"210",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "🏢 資本",
|
||||
label: "🏢 純資産",
|
||||
codes: ["601", "602"],
|
||||
},
|
||||
{
|
||||
label: "📈 収益",
|
||||
codes: ["701"],
|
||||
codes: ["701", "702", "703", "704", "705"],
|
||||
},
|
||||
{
|
||||
label: "📉 費用",
|
||||
codes: [
|
||||
"801",
|
||||
"802",
|
||||
"803",
|
||||
"804",
|
||||
"805",
|
||||
"806",
|
||||
"807",
|
||||
"808",
|
||||
"809",
|
||||
"810",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -410,9 +494,9 @@
|
||||
history = history.filter((d) => d !== description);
|
||||
history.unshift(description);
|
||||
|
||||
// 最大20件まで保存
|
||||
if (history.length > 20) {
|
||||
history = history.slice(0, 20);
|
||||
// 最大100件まで保存
|
||||
if (history.length > 100) {
|
||||
history = history.slice(0, 100);
|
||||
}
|
||||
|
||||
localStorage.setItem("descriptionHistory", JSON.stringify(history));
|
||||
@@ -462,10 +546,10 @@
|
||||
${buildAccountOptions()}
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text" class="debit right" ${
|
||||
<td><input type="text" class="debit right" inputmode="numeric" style="ime-mode: inactive;" ${
|
||||
isTaxRow ? "readonly" : ""
|
||||
} oninput="formatNumberInput(this)"></td>
|
||||
<td><input type="text" class="credit right" ${
|
||||
<td><input type="text" class="credit right" inputmode="numeric" style="ime-mode: inactive;" ${
|
||||
isTaxRow ? "readonly" : ""
|
||||
} oninput="formatNumberInput(this)"></td>
|
||||
<td>
|
||||
@@ -504,6 +588,7 @@
|
||||
const recalc = () => handleTax(tr);
|
||||
const debitInput = tr.querySelector(".debit");
|
||||
const creditInput = tr.querySelector(".credit");
|
||||
const accountSelect = tr.querySelector(".account");
|
||||
|
||||
debitInput.addEventListener("input", recalc);
|
||||
creditInput.addEventListener("input", recalc);
|
||||
@@ -512,6 +597,11 @@
|
||||
debitInput.addEventListener("blur", updateTotals);
|
||||
creditInput.addEventListener("blur", updateTotals);
|
||||
|
||||
// 科目選択時に履歴を保存
|
||||
accountSelect.addEventListener("change", () => {
|
||||
saveAccountToRecent(parseInt(accountSelect.value));
|
||||
});
|
||||
|
||||
tr.querySelector(".taxType").addEventListener("change", recalc);
|
||||
tr.querySelector(".taxDirection").addEventListener("change", recalc);
|
||||
}
|
||||
@@ -957,6 +1047,23 @@
|
||||
function buildAccountOptions() {
|
||||
let html = "";
|
||||
|
||||
// 最近使った科目を表示
|
||||
const recentAccountIds = JSON.parse(
|
||||
localStorage.getItem("recentAccounts") || "[]"
|
||||
);
|
||||
if (recentAccountIds.length > 0) {
|
||||
html += `<optgroup label="⭐ 最近使った科目">`;
|
||||
recentAccountIds.forEach((accountId) => {
|
||||
const account = accounts.find((a) => a.account_id === accountId);
|
||||
if (account) {
|
||||
html += `<option value="${account.account_id}">
|
||||
${account.account_code} ${account.account_name}
|
||||
</option>`;
|
||||
}
|
||||
});
|
||||
html += `</optgroup>`;
|
||||
}
|
||||
|
||||
// 已分组的科目
|
||||
ACCOUNT_GROUPS.forEach((group) => {
|
||||
html += `<optgroup label="${group.label}">`;
|
||||
@@ -989,9 +1096,189 @@
|
||||
return html;
|
||||
}
|
||||
|
||||
// 科目選択履歴を保存
|
||||
function saveAccountToRecent(accountId) {
|
||||
if (!accountId) return;
|
||||
|
||||
let recentAccounts = JSON.parse(
|
||||
localStorage.getItem("recentAccounts") || "[]"
|
||||
);
|
||||
|
||||
// 重複を除去して先頭に追加
|
||||
recentAccounts = recentAccounts.filter((id) => id !== accountId);
|
||||
recentAccounts.unshift(accountId);
|
||||
|
||||
// 最大20件まで保持
|
||||
if (recentAccounts.length > 20) {
|
||||
recentAccounts = recentAccounts.slice(0, 20);
|
||||
}
|
||||
|
||||
localStorage.setItem("recentAccounts", JSON.stringify(recentAccounts));
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 仕訳検索機能(journal-list.htmlから統合)
|
||||
// ========================================
|
||||
// 検索結果を印刷(検索を実行してから印刷)
|
||||
async function printSearchResults() {
|
||||
// まず検索を実行
|
||||
const from = document.getElementById("searchFromDate").value;
|
||||
const to = document.getElementById("searchToDate").value;
|
||||
const key = document.getElementById("searchKeyword").value;
|
||||
const accountId = document.getElementById("searchAccountId").value;
|
||||
const includeHistory = document.getElementById(
|
||||
"searchIncludeHistory"
|
||||
).checked;
|
||||
|
||||
const qs = new URLSearchParams();
|
||||
if (from) qs.append("from_date", from);
|
||||
if (to) qs.append("to_date", to);
|
||||
if (key) qs.append("keyword", key);
|
||||
if (accountId) qs.append("account_id", accountId);
|
||||
if (includeHistory) qs.append("include_history", "true");
|
||||
|
||||
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||
const data = await res.json();
|
||||
|
||||
// 印刷用に昇順でソート
|
||||
const printData = [...data].sort((a, b) => {
|
||||
if (a.entry_date !== b.entry_date) {
|
||||
return a.entry_date.localeCompare(b.entry_date);
|
||||
}
|
||||
return a.journal_entry_id - b.journal_entry_id;
|
||||
});
|
||||
|
||||
const printArea = document.getElementById("printArea");
|
||||
printArea.innerHTML = "";
|
||||
|
||||
// 月ごとにデータをグループ化
|
||||
const monthGroups = {};
|
||||
printData.forEach((e) => {
|
||||
const entryMonth = e.entry_date.substring(0, 7);
|
||||
if (!monthGroups[entryMonth]) {
|
||||
monthGroups[entryMonth] = [];
|
||||
}
|
||||
monthGroups[entryMonth].push(e);
|
||||
});
|
||||
|
||||
// 検索条件を生成
|
||||
const accountSelect = document.getElementById("searchAccountId");
|
||||
const accountName =
|
||||
accountSelect.options[accountSelect.selectedIndex]?.text || "";
|
||||
let conditions = [];
|
||||
if (from || to) {
|
||||
conditions.push(`期間: ${from || ""} ~ ${to || ""}`);
|
||||
}
|
||||
if (key) {
|
||||
conditions.push(`摘要: ${key}`);
|
||||
}
|
||||
if (accountName && accountName !== "-- すべて --") {
|
||||
conditions.push(`科目: ${accountName}`);
|
||||
}
|
||||
if (includeHistory) {
|
||||
conditions.push(`修正履歴を含む`);
|
||||
}
|
||||
const conditionsText =
|
||||
conditions.length > 0 ? conditions.join(" | ") : "検索条件: すべて";
|
||||
|
||||
// 月ごとにテーブルを生成(印刷用)
|
||||
Object.keys(monthGroups)
|
||||
.sort()
|
||||
.forEach((month, index) => {
|
||||
const monthData = monthGroups[month];
|
||||
const monthDisplay = new Date(month + "-01").toLocaleDateString(
|
||||
"ja-JP",
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
}
|
||||
);
|
||||
|
||||
const section = document.createElement("div");
|
||||
if (index > 0) {
|
||||
section.className = "page-break";
|
||||
}
|
||||
|
||||
section.innerHTML = `
|
||||
<h3 style="text-align: center; margin-bottom: 8px;">仕訳検索結果</h3>
|
||||
<div class="print-header">
|
||||
<div class="print-header-left">${conditionsText}</div>
|
||||
<div class="print-header-right">${monthDisplay}</div>
|
||||
</div>
|
||||
<table style="margin-bottom: 20px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日付</th>
|
||||
<th>摘要</th>
|
||||
<th>借方合計</th>
|
||||
<th>貸方合計</th>
|
||||
<th>税率</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="page-number">ページ ${index + 1}</div>
|
||||
`;
|
||||
|
||||
const tbody = section.querySelector("tbody");
|
||||
let monthDebitTotal = 0;
|
||||
let monthCreditTotal = 0;
|
||||
|
||||
monthData.forEach((e) => {
|
||||
monthDebitTotal += e.debit_total;
|
||||
monthCreditTotal += e.credit_total;
|
||||
|
||||
const versionInfo =
|
||||
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${e.entry_date}</td>
|
||||
<td style="text-align:left">${e.description}${versionInfo}</td>
|
||||
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||
<td style="text-align:right">${e.credit_total.toLocaleString()}</td>
|
||||
<td style="text-align:center">${
|
||||
e.tax_rates ? e.tax_rates + "%" : ""
|
||||
}</td>
|
||||
<td>
|
||||
<button onclick="viewJournal(${
|
||||
e.journal_entry_id
|
||||
})">表示</button>
|
||||
<button onclick="copyJournal(${
|
||||
e.journal_entry_id
|
||||
})" style="color: green;">複製</button>
|
||||
<button onclick="reverseAndEdit(${
|
||||
e.journal_entry_id
|
||||
})">修正</button>
|
||||
<button onclick="deleteJournalEntry(${
|
||||
e.journal_entry_id
|
||||
})" style="color: red;">削除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
// 月合計行を追加
|
||||
const totalRow = document.createElement("tr");
|
||||
totalRow.style.fontWeight = "bold";
|
||||
totalRow.style.backgroundColor = "#f0f0f0";
|
||||
totalRow.innerHTML = `
|
||||
<td colspan="2" style="text-align:right">合計:</td>
|
||||
<td style="text-align:right">${monthDebitTotal.toLocaleString()}</td>
|
||||
<td style="text-align:right">${monthCreditTotal.toLocaleString()}</td>
|
||||
<td colspan="2"></td>
|
||||
`;
|
||||
tbody.appendChild(totalRow);
|
||||
|
||||
printArea.appendChild(section);
|
||||
});
|
||||
|
||||
// 少し待ってから印刷(データの表示を待つ)
|
||||
setTimeout(() => {
|
||||
window.print();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
async function searchJournals() {
|
||||
const from = document.getElementById("searchFromDate").value;
|
||||
const to = document.getElementById("searchToDate").value;
|
||||
@@ -1023,15 +1310,95 @@
|
||||
const res = await fetch(`${API}/journal-entries?${qs.toString()}`);
|
||||
const data = await res.json();
|
||||
|
||||
const tbody = document.getElementById("searchResultList");
|
||||
tbody.innerHTML = "";
|
||||
const printArea = document.getElementById("printArea");
|
||||
printArea.innerHTML = "";
|
||||
|
||||
// 月ごとにデータをグループ化
|
||||
const monthGroups = {};
|
||||
data.forEach((e) => {
|
||||
const tr = document.createElement("tr");
|
||||
// 修正履歴がある場合はバージョン番号を表示
|
||||
const versionInfo =
|
||||
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
||||
tr.innerHTML = `
|
||||
const entryMonth = e.entry_date.substring(0, 7); // YYYY-MM
|
||||
if (!monthGroups[entryMonth]) {
|
||||
monthGroups[entryMonth] = [];
|
||||
}
|
||||
monthGroups[entryMonth].push(e);
|
||||
});
|
||||
|
||||
// 検索条件を生成
|
||||
const conditionsText = (() => {
|
||||
let conditions = [];
|
||||
if (from || to) {
|
||||
conditions.push(`期間: ${from || ""} ~ ${to || ""}`);
|
||||
}
|
||||
if (key) {
|
||||
conditions.push(`摘要: ${key}`);
|
||||
}
|
||||
const accountSelect = document.getElementById("searchAccountId");
|
||||
const accountName =
|
||||
accountSelect.options[accountSelect.selectedIndex]?.text || "";
|
||||
if (accountName && accountName !== "-- すべて --") {
|
||||
conditions.push(`科目: ${accountName}`);
|
||||
}
|
||||
if (includeHistory) {
|
||||
conditions.push(`修正履歴を含む`);
|
||||
}
|
||||
return conditions.length > 0
|
||||
? conditions.join(" | ")
|
||||
: "検索条件: すべて";
|
||||
})();
|
||||
|
||||
// 月ごとにテーブルを生成(画面表示は降順)
|
||||
Object.keys(monthGroups)
|
||||
.sort((a, b) => b.localeCompare(a)) // 月を降順で表示
|
||||
.forEach((month, index) => {
|
||||
const monthData = monthGroups[month];
|
||||
const monthDisplay = new Date(month + "-01").toLocaleDateString(
|
||||
"ja-JP",
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
}
|
||||
);
|
||||
|
||||
const section = document.createElement("div");
|
||||
if (index > 0) {
|
||||
section.className = "page-break";
|
||||
}
|
||||
|
||||
section.innerHTML = `
|
||||
<h3 style="text-align: center; margin-bottom: 8px;">仕訳検索結果</h3>
|
||||
<div class="print-header">
|
||||
<div class="print-header-left">${conditionsText}</div>
|
||||
<div class="print-header-right">${monthDisplay}</div>
|
||||
</div>
|
||||
<table style="margin-bottom: 20px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日付</th>
|
||||
<th>摘要</th>
|
||||
<th>借方合計</th>
|
||||
<th>貸方合計</th>
|
||||
<th>税率</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="page-number">ページ ${index + 1}</div>
|
||||
`;
|
||||
|
||||
const tbody = section.querySelector("tbody");
|
||||
let monthDebitTotal = 0;
|
||||
let monthCreditTotal = 0;
|
||||
|
||||
// 月内のデータも降順で表示(画面表示用)
|
||||
monthData.forEach((e) => {
|
||||
monthDebitTotal += e.debit_total;
|
||||
monthCreditTotal += e.credit_total;
|
||||
|
||||
const versionInfo =
|
||||
includeHistory && e.version > 1 ? ` (v${e.version})` : "";
|
||||
const tr = document.createElement("tr");
|
||||
tr.innerHTML = `
|
||||
<td>${e.entry_date}</td>
|
||||
<td style="text-align:left">${e.description}${versionInfo}</td>
|
||||
<td style="text-align:right">${e.debit_total.toLocaleString()}</td>
|
||||
@@ -1054,8 +1421,23 @@
|
||||
})" style="color: red;">削除</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
|
||||
// 月合計行を追加
|
||||
const totalRow = document.createElement("tr");
|
||||
totalRow.style.fontWeight = "bold";
|
||||
totalRow.style.backgroundColor = "#f0f0f0";
|
||||
totalRow.innerHTML = `
|
||||
<td colspan="2" style="text-align:right">合計:</td>
|
||||
<td style="text-align:right">${monthDebitTotal.toLocaleString()}</td>
|
||||
<td style="text-align:right">${monthCreditTotal.toLocaleString()}</td>
|
||||
<td colspan="2"></td>
|
||||
`;
|
||||
tbody.appendChild(totalRow);
|
||||
|
||||
printArea.appendChild(section);
|
||||
});
|
||||
}
|
||||
|
||||
function viewJournal(id) {
|
||||
|
||||
Reference in New Issue
Block a user