chore: 年度リストを昇順に変更
This commit is contained in:
@@ -405,6 +405,29 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ── 印刷ヘッダー ── */
|
||||
.print-header {
|
||||
display: none;
|
||||
}
|
||||
@media print {
|
||||
.print-header {
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 2px solid #000;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.print-header .ph-company {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.print-header .ph-sub {
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 印刷スタイル ── */
|
||||
@media print {
|
||||
.no-print {
|
||||
@@ -434,50 +457,133 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 印刷時のみ表示されるヘッダー -->
|
||||
<div class="print-header">
|
||||
<div class="ph-company" id="printCompany"></div>
|
||||
<div class="ph-sub">
|
||||
<span id="printBank"></span>
|
||||
<span id="printDate"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-header no-print">
|
||||
<h2>🏦 銀行明細照会</h2>
|
||||
<div class="header-btns">
|
||||
<button class="btn btn-orange" onclick="openImportModal()">
|
||||
CSV取込
|
||||
</button>
|
||||
<button class="btn btn-blue" onclick="window.print()">印刷</button>
|
||||
<button class="btn btn-blue" onclick="doPrint()">印刷</button>
|
||||
<a class="btn btn-gray" href="index.html">← メニューへ戻る</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 印刷設定(画面のみ) -->
|
||||
<div
|
||||
class="search-form no-print"
|
||||
style="padding: 10px 18px; background: #fff8e1; border-color: #ffe082"
|
||||
>
|
||||
<label style="font-weight: bold; white-space: nowrap">印刷設定:</label>
|
||||
<label style="white-space: nowrap">会社名:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="printCompanyInput"
|
||||
style="
|
||||
width: 220px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
"
|
||||
placeholder="例:株式会社〇〇"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="msgBar" class="msg-bar no-print"></div>
|
||||
|
||||
<!-- 検索フォーム -->
|
||||
<div class="search-form no-print">
|
||||
<label>年:</label>
|
||||
<input
|
||||
type="number"
|
||||
id="filterYear"
|
||||
min="2000"
|
||||
max="2099"
|
||||
style="width: 80px"
|
||||
/>
|
||||
<label>月:</label>
|
||||
<select id="filterMonth">
|
||||
<option value="">全月</option>
|
||||
<option value="1">1月</option>
|
||||
<option value="2">2月</option>
|
||||
<option value="3">3月</option>
|
||||
<option value="4">4月</option>
|
||||
<option value="5">5月</option>
|
||||
<option value="6">6月</option>
|
||||
<option value="7">7月</option>
|
||||
<option value="8">8月</option>
|
||||
<option value="9">9月</option>
|
||||
<option value="10">10月</option>
|
||||
<option value="11">11月</option>
|
||||
<option value="12">12月</option>
|
||||
</select>
|
||||
<label>銀行:</label>
|
||||
<select id="filterBank">
|
||||
<option value="">全銀行</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="loadData()">検索</button>
|
||||
<div
|
||||
class="search-form no-print"
|
||||
style="flex-direction: column; align-items: flex-start; gap: 10px"
|
||||
>
|
||||
<div
|
||||
style="display: flex; align-items: center; flex-wrap: wrap; gap: 8px"
|
||||
>
|
||||
<label style="min-width: 3em">開始:</label>
|
||||
<input
|
||||
type="number"
|
||||
id="startYear"
|
||||
min="2000"
|
||||
max="2099"
|
||||
style="width: 72px"
|
||||
placeholder="年"
|
||||
/><span>年</span>
|
||||
<select id="startMonth" style="width: 60px">
|
||||
<option value="">-</option>
|
||||
<option value="1">1月</option>
|
||||
<option value="2">2月</option>
|
||||
<option value="3">3月</option>
|
||||
<option value="4">4月</option>
|
||||
<option value="5">5月</option>
|
||||
<option value="6">6月</option>
|
||||
<option value="7">7月</option>
|
||||
<option value="8">8月</option>
|
||||
<option value="9">9月</option>
|
||||
<option value="10">10月</option>
|
||||
<option value="11">11月</option>
|
||||
<option value="12">12月</option>
|
||||
</select>
|
||||
<input
|
||||
type="number"
|
||||
id="startDay"
|
||||
min="1"
|
||||
max="31"
|
||||
style="width: 48px"
|
||||
placeholder="日"
|
||||
/><span>日</span>
|
||||
<span style="margin: 0 6px; color: #999">〜</span>
|
||||
<label style="min-width: 3em">終了:</label>
|
||||
<input
|
||||
type="number"
|
||||
id="endYear"
|
||||
min="2000"
|
||||
max="2099"
|
||||
style="width: 72px"
|
||||
placeholder="年"
|
||||
/><span>年</span>
|
||||
<select id="endMonth" style="width: 60px">
|
||||
<option value="">-</option>
|
||||
<option value="1">1月</option>
|
||||
<option value="2">2月</option>
|
||||
<option value="3">3月</option>
|
||||
<option value="4">4月</option>
|
||||
<option value="5">5月</option>
|
||||
<option value="6">6月</option>
|
||||
<option value="7">7月</option>
|
||||
<option value="8">8月</option>
|
||||
<option value="9">9月</option>
|
||||
<option value="10">10月</option>
|
||||
<option value="11">11月</option>
|
||||
<option value="12">12月</option>
|
||||
</select>
|
||||
<input
|
||||
type="number"
|
||||
id="endDay"
|
||||
min="1"
|
||||
max="31"
|
||||
style="width: 48px"
|
||||
placeholder="日"
|
||||
/><span>日</span>
|
||||
</div>
|
||||
<div
|
||||
style="display: flex; align-items: center; flex-wrap: wrap; gap: 8px"
|
||||
>
|
||||
<label>銀行:</label>
|
||||
<select id="filterBank">
|
||||
<option value="">全銀行</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="loadData()">検索</button>
|
||||
<button class="btn btn-gray" onclick="clearDateFilter()">クリア</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 月別チップ -->
|
||||
@@ -638,16 +744,114 @@
|
||||
}
|
||||
|
||||
// ── 明細データ読み込み ──────────────────────────────────
|
||||
function _lastDayOfMonth(year, month) {
|
||||
return new Date(parseInt(year), parseInt(month), 0).getDate();
|
||||
}
|
||||
|
||||
// 会社名をlocalStorageに保存・復元
|
||||
const COMPANY_KEY = "bankPrintCompanyName";
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const saved = localStorage.getItem(COMPANY_KEY) || "";
|
||||
const input = document.getElementById("printCompanyInput");
|
||||
if (input && saved) input.value = saved;
|
||||
input &&
|
||||
input.addEventListener("input", function () {
|
||||
localStorage.setItem(COMPANY_KEY, this.value);
|
||||
});
|
||||
});
|
||||
|
||||
function doPrint() {
|
||||
const company = (
|
||||
document.getElementById("printCompanyInput").value || ""
|
||||
).trim();
|
||||
const bankVal = document.getElementById("filterBank").value;
|
||||
|
||||
// 期間ラベル生成
|
||||
const sy = document.getElementById("startYear").value;
|
||||
const sm = document.getElementById("startMonth").value;
|
||||
const sd = document.getElementById("startDay").value;
|
||||
const ey = document.getElementById("endYear").value;
|
||||
const em = document.getElementById("endMonth").value;
|
||||
const ed = document.getElementById("endDay").value;
|
||||
let periodLabel = "";
|
||||
if (sy || ey) {
|
||||
const from = sy
|
||||
? sy +
|
||||
(sm ? "/" + String(sm).padStart(2, "0") : "") +
|
||||
(sd ? "/" + String(sd).padStart(2, "0") : "")
|
||||
: "";
|
||||
const to = ey
|
||||
? ey +
|
||||
(em ? "/" + String(em).padStart(2, "0") : "") +
|
||||
(ed ? "/" + String(ed).padStart(2, "0") : "")
|
||||
: "";
|
||||
if (from && to) periodLabel = " (" + from + " 〜 " + to + ")";
|
||||
else if (from) periodLabel = " (" + from + " 〜)";
|
||||
else if (to) periodLabel = " (〜 " + to + ")";
|
||||
}
|
||||
|
||||
// 印刷ヘッダーを設定
|
||||
document.getElementById("printCompany").textContent = company;
|
||||
document.getElementById("printBank").textContent = bankVal
|
||||
? "銀行:" + bankVal + periodLabel
|
||||
: "期間:全期間" + periodLabel || "全期間";
|
||||
const today = new Date();
|
||||
const dateStr =
|
||||
today.getFullYear() +
|
||||
"年" +
|
||||
(today.getMonth() + 1) +
|
||||
"月" +
|
||||
today.getDate() +
|
||||
"日";
|
||||
document.getElementById("printDate").textContent = "印刷日:" + dateStr;
|
||||
|
||||
window.print();
|
||||
}
|
||||
|
||||
function clearDateFilter() {
|
||||
[
|
||||
"startYear",
|
||||
"startMonth",
|
||||
"startDay",
|
||||
"endYear",
|
||||
"endMonth",
|
||||
"endDay",
|
||||
].forEach((id) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = "";
|
||||
});
|
||||
loadData();
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const year = document.getElementById("filterYear").value;
|
||||
const month = document.getElementById("filterMonth").value;
|
||||
const sy = document.getElementById("startYear").value.trim();
|
||||
const sm = document.getElementById("startMonth").value;
|
||||
const sd = document.getElementById("startDay").value.trim();
|
||||
const ey = document.getElementById("endYear").value.trim();
|
||||
const em = document.getElementById("endMonth").value;
|
||||
const ed = document.getElementById("endDay").value.trim();
|
||||
const bank = document.getElementById("filterBank").value;
|
||||
|
||||
hideMsg("msgBar");
|
||||
|
||||
const qp = [];
|
||||
if (year) qp.push(`year=${year}`);
|
||||
if (month) qp.push(`month=${month}`);
|
||||
if (sy) {
|
||||
const m = sm ? String(sm).padStart(2, "0") : "01";
|
||||
const d = sd ? String(sd).padStart(2, "0") : "01";
|
||||
qp.push(`date_from=${sy}-${m}-${d}`);
|
||||
}
|
||||
if (ey) {
|
||||
const m = em ? String(em).padStart(2, "0") : "12";
|
||||
let d;
|
||||
if (ed) {
|
||||
d = String(ed).padStart(2, "0");
|
||||
} else if (em) {
|
||||
d = String(_lastDayOfMonth(ey, em)).padStart(2, "0");
|
||||
} else {
|
||||
d = "31";
|
||||
}
|
||||
qp.push(`date_to=${ey}-${m}-${d}`);
|
||||
}
|
||||
if (bank) qp.push(`bank_name=${encodeURIComponent(bank)}`);
|
||||
const url = "/bank-statements" + (qp.length ? "?" + qp.join("&") : "");
|
||||
|
||||
@@ -658,7 +862,7 @@
|
||||
|
||||
_allRows = data.rows;
|
||||
_currentPage = 1;
|
||||
renderMonthlyChips(data.monthly, year, month);
|
||||
renderMonthlyChips(data.monthly, { sy, sm, sd, ey, em, ed });
|
||||
renderStats();
|
||||
renderTable();
|
||||
updateBankFilter(data.banks);
|
||||
@@ -682,18 +886,23 @@
|
||||
}
|
||||
|
||||
// ── 月別チップ ──────────────────────────────────────────
|
||||
function renderMonthlyChips(monthly, selYear, selMonth) {
|
||||
function renderMonthlyChips(monthly, filter) {
|
||||
const { sy, sm, sd, ey, em, ed } = filter || {};
|
||||
const wrap = document.getElementById("monthlyChips");
|
||||
wrap.innerHTML = "";
|
||||
if (!monthly.length) return;
|
||||
|
||||
// 「全月」チップ
|
||||
// 「全月」チップ:開始・終了が同じ年で月未指定の場合にアクティブ
|
||||
const noMonthSelected = !sm && !em && !sd && !ed;
|
||||
const allChip = document.createElement("span");
|
||||
allChip.className =
|
||||
"month-chip no-print" + (!selMonth ? " active" : "");
|
||||
"month-chip no-print" + (noMonthSelected ? " active" : "");
|
||||
allChip.textContent = "全月";
|
||||
allChip.onclick = () => {
|
||||
document.getElementById("filterMonth").value = "";
|
||||
document.getElementById("startMonth").value = "";
|
||||
document.getElementById("startDay").value = "";
|
||||
document.getElementById("endMonth").value = "";
|
||||
document.getElementById("endDay").value = "";
|
||||
loadData();
|
||||
};
|
||||
wrap.appendChild(allChip);
|
||||
@@ -702,15 +911,22 @@
|
||||
const [y, mo] = m.ym.split("-");
|
||||
const chip = document.createElement("span");
|
||||
const isActive =
|
||||
String(selYear) === y && String(selMonth) === String(parseInt(mo));
|
||||
sy === y &&
|
||||
ey === y &&
|
||||
String(parseInt(sm || "0")) === String(parseInt(mo)) &&
|
||||
String(parseInt(em || "0")) === String(parseInt(mo)) &&
|
||||
!sd &&
|
||||
!ed;
|
||||
chip.className = "month-chip no-print" + (isActive ? " active" : "");
|
||||
chip.innerHTML = `${parseInt(mo)}月 <small style="color:inherit;opacity:.8">${
|
||||
m.count
|
||||
}件</small>`;
|
||||
chip.innerHTML = `${parseInt(mo)}月 <small style="color:inherit;opacity:.8">${m.count}件</small>`;
|
||||
chip.title = `出金 ${fmt(m.total_debit)} / 入金 ${fmt(m.total_credit)}`;
|
||||
chip.onclick = () => {
|
||||
document.getElementById("filterYear").value = y;
|
||||
document.getElementById("filterMonth").value = parseInt(mo);
|
||||
document.getElementById("startYear").value = y;
|
||||
document.getElementById("startMonth").value = parseInt(mo);
|
||||
document.getElementById("startDay").value = "";
|
||||
document.getElementById("endYear").value = y;
|
||||
document.getElementById("endMonth").value = parseInt(mo);
|
||||
document.getElementById("endDay").value = "";
|
||||
loadData();
|
||||
};
|
||||
wrap.appendChild(chip);
|
||||
|
||||
@@ -376,5 +376,4 @@ html {
|
||||
padding: 20px 16px !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -279,6 +279,72 @@
|
||||
background: #f3eeff;
|
||||
color: #6f42c1;
|
||||
}
|
||||
/* テーブル */
|
||||
.tbl-wrap {
|
||||
overflow-x: auto;
|
||||
margin-top: 12px;
|
||||
}
|
||||
table.apply-tbl {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
}
|
||||
table.apply-tbl th,
|
||||
table.apply-tbl td {
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 5px 8px;
|
||||
white-space: nowrap;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.apply-tbl th {
|
||||
background: #f1f3f5;
|
||||
font-weight: 700;
|
||||
}
|
||||
table.apply-tbl tr:hover td {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.proc-select {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.arrive-badge {
|
||||
font-weight: 700;
|
||||
color: #0a3622;
|
||||
background: #d1e7dd;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.info-box {
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.env-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.env-sandbox {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
.env-production {
|
||||
background: #d1e7dd;
|
||||
color: #0a3622;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -422,6 +488,193 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 申請書作成(最終確認試験用) ───────────────────── -->
|
||||
<div class="card" id="applyCard">
|
||||
<h2>📝 テスト申請書作成(最終確認試験用)</h2>
|
||||
<p class="desc">
|
||||
検証環境へテスト手続を送信して到達番号を取得します。<br />
|
||||
取得した到達番号は試験データ表と Excel
|
||||
に記入してデジタル庁へ送付してください。
|
||||
</p>
|
||||
|
||||
<div class="info-box">
|
||||
⚠️ ログイン後に操作可能です。<br />
|
||||
環境:
|
||||
<span id="envLabel" class="env-badge env-sandbox">sandbox(検証)</span>
|
||||
ソフトウェアID:
|
||||
<code id="swIdLabel" style="font-size: 11px">—</code>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>手続識別子(対象手続を選択)</label>
|
||||
<select class="proc-select" id="procSelect">
|
||||
<optgroup label="電子送達関係">
|
||||
<option value="900A013800001000">
|
||||
900A013800001000 – APIテスト用手続(電子送達)0001(電子送達1件)
|
||||
</option>
|
||||
</optgroup>
|
||||
<optgroup label="社会保険関係 標準形式">
|
||||
<option value="950A010002010000">
|
||||
950A010002010000 – APIテスト(社保通)1007 公文書(署名あり)
|
||||
</option>
|
||||
<option value="950A010002011000">
|
||||
950A010002011000 – APIテスト(社保通)1008 公文書(署名なし)
|
||||
</option>
|
||||
<option value="950A010700001000">
|
||||
950A010700001000 – APIテスト(社保通)1001 手続終了
|
||||
</option>
|
||||
<option value="950A010700002000">
|
||||
950A010700002000 – APIテスト(社保通)1002 手続終了
|
||||
</option>
|
||||
<option value="950A010700005000">
|
||||
950A010700005000 – APIテスト(社保通)1003 取下げ承認
|
||||
</option>
|
||||
<option value="950A010700006000">
|
||||
950A010700006000 – APIテスト(社保通)1004 取下げ却下
|
||||
</option>
|
||||
</optgroup>
|
||||
<optgroup label="労働保険関係 標準形式">
|
||||
<option value="950A010200003000">
|
||||
950A010200003000 – APIテスト(労保通)1001 手続終了
|
||||
</option>
|
||||
<option value="950A010200004000">
|
||||
950A010200004000 – APIテスト(労保通)1002 手続終了
|
||||
</option>
|
||||
<option value="950A010002012000">
|
||||
950A010002012000 – APIテスト(労保通)1004 コメント(メッセージ)
|
||||
</option>
|
||||
<option value="950A010002016000">
|
||||
950A010002016000 – APIテスト(労保通)1008 コメント(公文書)
|
||||
</option>
|
||||
</optgroup>
|
||||
<optgroup label="社会保険関係 個別署名形式">
|
||||
<option value="950A101220029000">
|
||||
950A101220029000 – APIテスト(社保個)1001 手続終了
|
||||
</option>
|
||||
<option value="950A102200038000">
|
||||
950A102200038000 – APIテスト(社保個)1005 コメント(メッセージ)
|
||||
</option>
|
||||
<option value="950A102200039000">
|
||||
950A102200039000 – APIテスト(社保個)1006 公文書(署名あり)
|
||||
</option>
|
||||
</optgroup>
|
||||
<optgroup label="その他">
|
||||
<option value="900A020700013000">
|
||||
900A020700013000 – APIテスト(労保適用)0003 補正申請
|
||||
</option>
|
||||
<option value="900A102800072000">
|
||||
900A102800072000 – APIテスト(労保適用個)0014 プレ印字
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 14px;
|
||||
"
|
||||
>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
id="applyBtn"
|
||||
onclick="doTestApply()"
|
||||
style="background: #6f42c1"
|
||||
>
|
||||
📤 テスト申請を送信
|
||||
</button>
|
||||
<span
|
||||
id="applySpinner"
|
||||
style="display: none; font-size: 13px; color: #555"
|
||||
>⏳ 送信中...</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<!-- 結果テーブル -->
|
||||
<div id="applyResult" style="display: none">
|
||||
<div class="dl-result" style="margin-bottom: 10px">
|
||||
<span>✅ 到達番号: </span>
|
||||
<strong
|
||||
id="arriveIdResult"
|
||||
class="arrive-badge"
|
||||
title="クリックでコピー"
|
||||
onclick="copyArriveId()"
|
||||
></strong>
|
||||
<span
|
||||
style="font-size: 12px; color: #6c757d; margin-left: 8px"
|
||||
id="arriveDateResult"
|
||||
></span>
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #555" id="procNameResult"></div>
|
||||
</div>
|
||||
<div
|
||||
id="applyError"
|
||||
style="
|
||||
display: none;
|
||||
color: #842029;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #f8d7da;
|
||||
border-radius: 4px;
|
||||
"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- ─── 申請案件一覧 ─────────────────────────────────────── -->
|
||||
<div class="card" id="listCard">
|
||||
<h2>📋 申請案件一覧(到達番号確認)</h2>
|
||||
<p class="desc">
|
||||
送信済みの申請案件一覧を表示します。到達番号を Excel
|
||||
に転記してデジタル庁へ提出してください。
|
||||
</p>
|
||||
<div
|
||||
style="display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap"
|
||||
>
|
||||
<button class="btn btn-secondary" onclick="loadApplyList()">
|
||||
🔄 一覧を更新
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
onclick="exportApplyListCsv()"
|
||||
style="background: #0d6efd; color: #fff"
|
||||
>
|
||||
📥 CSV エクスポート
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="listStatus"
|
||||
style="font-size: 13px; color: #6c757d; margin-bottom: 8px"
|
||||
></div>
|
||||
<div class="tbl-wrap" id="applyListWrap">
|
||||
<table class="apply-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>到達番号</th>
|
||||
<th>到達日時</th>
|
||||
<th>手続識別子</th>
|
||||
<th>手続名</th>
|
||||
<th>ステータス</th>
|
||||
<th>サブステータス</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="applyListBody">
|
||||
<tr>
|
||||
<td
|
||||
colspan="7"
|
||||
style="text-align: center; color: #aaa; padding: 20px"
|
||||
>
|
||||
「一覧を更新」ボタンで取得してください
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let timer = null;
|
||||
let currentTab = "api";
|
||||
@@ -677,10 +930,174 @@
|
||||
const r = await fetch("/egov/status");
|
||||
const d = await r.json();
|
||||
apply(d);
|
||||
// 環境・ソフトウェアID を表示
|
||||
document.getElementById("envLabel").textContent =
|
||||
d.environment === "sandbox"
|
||||
? "sandbox(検証環境)"
|
||||
: "production(本番環境)";
|
||||
document.getElementById("envLabel").className =
|
||||
"env-badge " +
|
||||
(d.environment === "sandbox" ? "env-sandbox" : "env-production");
|
||||
if (!["idle", "logged_in", "error"].includes(d.status))
|
||||
startPolling();
|
||||
} catch (_) {}
|
||||
|
||||
// ソフトウェアID 表示
|
||||
try {
|
||||
const r2 = await fetch("/egov/config-info");
|
||||
if (r2.ok) {
|
||||
const cfg = await r2.json();
|
||||
document.getElementById("swIdLabel").textContent =
|
||||
cfg.software_id || "—";
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
|
||||
// ─── テスト申請書作成 ────────────────────────────────────
|
||||
let _applyResults = [];
|
||||
|
||||
async function doTestApply() {
|
||||
const procId = document.getElementById("procSelect").value;
|
||||
const applyBtn = document.getElementById("applyBtn");
|
||||
const spinner = document.getElementById("applySpinner");
|
||||
const resultDiv = document.getElementById("applyResult");
|
||||
const errorDiv = document.getElementById("applyError");
|
||||
|
||||
applyBtn.disabled = true;
|
||||
show("applySpinner");
|
||||
resultDiv.style.display = "none";
|
||||
errorDiv.style.display = "none";
|
||||
|
||||
try {
|
||||
const res = await fetch("/egov/test-apply", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ proc_id: procId }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
errorDiv.textContent =
|
||||
"エラー: " + (data.detail || JSON.stringify(data));
|
||||
errorDiv.style.display = "block";
|
||||
return;
|
||||
}
|
||||
document.getElementById("arriveIdResult").textContent =
|
||||
data.arrive_id || "(取得中)";
|
||||
document.getElementById("arriveDateResult").textContent =
|
||||
data.arrive_date ? ` 到達: ${data.arrive_date}` : "";
|
||||
document.getElementById("procNameResult").textContent =
|
||||
data.proc_name || "";
|
||||
resultDiv.style.display = "block";
|
||||
|
||||
// 送信済みリストに追加
|
||||
_applyResults.unshift({
|
||||
arrive_id: data.arrive_id,
|
||||
arrive_date: data.arrive_date,
|
||||
proc_id: procId,
|
||||
proc_name: data.proc_name,
|
||||
status: "",
|
||||
sub_status: "",
|
||||
});
|
||||
renderApplyList(_applyResults);
|
||||
} catch (err) {
|
||||
errorDiv.textContent = "通信エラー: " + err.message;
|
||||
errorDiv.style.display = "block";
|
||||
} finally {
|
||||
applyBtn.disabled = false;
|
||||
hide("applySpinner");
|
||||
}
|
||||
}
|
||||
|
||||
function copyArriveId() {
|
||||
const val = document.getElementById("arriveIdResult").textContent;
|
||||
navigator.clipboard
|
||||
.writeText(val)
|
||||
.then(() => alert("コピーしました: " + val))
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// ─── 申請案件一覧 ────────────────────────────────────────
|
||||
async function loadApplyList() {
|
||||
const statusDiv = document.getElementById("listStatus");
|
||||
statusDiv.textContent = "⏳ 一覧を取得中...";
|
||||
try {
|
||||
const res = await fetch("/egov/apply-list");
|
||||
if (!res.ok) {
|
||||
const e = await res.json();
|
||||
statusDiv.textContent = "エラー: " + (e.detail || "不明");
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
_applyResults = data.apply_list || [];
|
||||
renderApplyList(_applyResults);
|
||||
statusDiv.textContent = `全 ${data.total} 件`;
|
||||
} catch (err) {
|
||||
statusDiv.textContent = "通信エラー: " + err.message;
|
||||
}
|
||||
}
|
||||
|
||||
function renderApplyList(items) {
|
||||
const tbody = document.getElementById("applyListBody");
|
||||
if (!items.length) {
|
||||
tbody.innerHTML =
|
||||
'<tr><td colspan="7" style="text-align:center;color:#aaa;padding:20px;">申請案件はありません</td></tr>';
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = items
|
||||
.map(
|
||||
(it, i) => `
|
||||
<tr>
|
||||
<td>${i + 1}</td>
|
||||
<td><span class="arrive-badge" onclick="navigator.clipboard.writeText('${esc(it.arrive_id)}')" title="クリックでコピー">${esc(it.arrive_id)}</span></td>
|
||||
<td>${esc(it.arrive_date)}</td>
|
||||
<td style="font-family:monospace;font-size:11px">${esc(it.proc_id)}</td>
|
||||
<td>${esc(it.proc_name)}</td>
|
||||
<td>${esc(it.status)}</td>
|
||||
<td>${esc(it.sub_status)}</td>
|
||||
</tr>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return String(s || "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function exportApplyListCsv() {
|
||||
if (!_applyResults.length) {
|
||||
alert("データがありません");
|
||||
return;
|
||||
}
|
||||
const header = [
|
||||
"到達番号",
|
||||
"到達日時",
|
||||
"手続識別子",
|
||||
"手続名",
|
||||
"ステータス",
|
||||
"サブステータス",
|
||||
];
|
||||
const rows = _applyResults.map((it) =>
|
||||
[
|
||||
it.arrive_id,
|
||||
it.arrive_date,
|
||||
it.proc_id,
|
||||
it.proc_name,
|
||||
it.status,
|
||||
it.sub_status,
|
||||
]
|
||||
.map((v) => `"${String(v || "").replace(/"/g, '""')}"`)
|
||||
.join(","),
|
||||
);
|
||||
const csv = "\uFEFF" + [header.join(","), ...rows].join("\r\n");
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
|
||||
link.download = "egov_apply_list.csv";
|
||||
link.click();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -254,6 +254,41 @@ function initTrialBalance() {
|
||||
}
|
||||
}
|
||||
|
||||
// 年度ドロップダウンを読み込む
|
||||
async function loadFiscalYears() {
|
||||
try {
|
||||
const res = await fetch("/trial-balance/fiscal-years");
|
||||
if (!res.ok) return;
|
||||
const years = await res.json();
|
||||
const sel = document.getElementById("fiscalYear");
|
||||
if (!sel) return;
|
||||
years.forEach((fy) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = JSON.stringify({
|
||||
date_from: fy.date_from,
|
||||
date_to: fy.date_to,
|
||||
});
|
||||
opt.textContent = fy.label;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn("年度一覧取得失敗:", e);
|
||||
}
|
||||
}
|
||||
loadFiscalYears();
|
||||
|
||||
// 年度選択 → 開始・終了日を自動セット(ユーザーは自由に変更可能)
|
||||
document.getElementById("fiscalYear").addEventListener("change", function () {
|
||||
if (!this.value) return;
|
||||
try {
|
||||
const { date_from, date_to } = JSON.parse(this.value);
|
||||
document.getElementById("dateFrom").value = date_from;
|
||||
document.getElementById("dateTo").value = date_to;
|
||||
} catch (e) {
|
||||
/* ignore */
|
||||
}
|
||||
});
|
||||
|
||||
// 檢索按鈕的事件
|
||||
document.getElementById("btnSearch").addEventListener("click", () => {
|
||||
const dateFrom = document.getElementById("dateFrom").value;
|
||||
|
||||
@@ -1331,7 +1331,7 @@
|
||||
</div>
|
||||
<div class="calc-note" style="background:#eef6ee; border-left:3px solid #28a745; padding:6px 10px; margin:6px 0; font-size:0.88em; color:#1a5c1a; line-height:1.7">
|
||||
健康保険 ¥${_health.toLocaleString()} + 介護保険 ¥${_care.toLocaleString()} + 子ども・子育て支援金 ¥${_child.toLocaleString()} = <strong>¥${_healthCareChild.toLocaleString()}</strong><br>
|
||||
¥${_healthCareChild.toLocaleString()}(上記合計) + 厚生年金 ¥${_pension.toLocaleString()}${' '}${_emp > 0 ? `+ 雇用保険 ¥${_emp.toLocaleString()}` : ''}= <strong>社会保険小計 ¥${_socialSub.toLocaleString()}</strong>
|
||||
¥${_healthCareChild.toLocaleString()}(上記合計) + 厚生年金 ¥${_pension.toLocaleString()}${" "}${_emp > 0 ? `+ 雇用保険 ¥${_emp.toLocaleString()}` : ""}= <strong>社会保険小計 ¥${_socialSub.toLocaleString()}</strong>
|
||||
</div>
|
||||
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_socialSub.toLocaleString()}</span></div>
|
||||
<div class="detail-row">
|
||||
@@ -1379,7 +1379,12 @@
|
||||
document.getElementById("payrollDetail").style.display = "block";
|
||||
// モバイルでは明細パネルへ自動スクロール
|
||||
if (window.innerWidth <= 767 && splitRightSalary) {
|
||||
setTimeout(function() { splitRightSalary.scrollIntoView({ behavior: "smooth", block: "start" }); }, 50);
|
||||
setTimeout(function () {
|
||||
splitRightSalary.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("給与明細の取得に失敗しました");
|
||||
@@ -1918,7 +1923,7 @@
|
||||
</div>
|
||||
<div class="calc-note" style="background:#eef6ee; border-left:3px solid #28a745; padding:6px 10px; margin:6px 0; font-size:0.88em; color:#1a5c1a; line-height:1.7">
|
||||
健康保険 ¥${_bHealth.toLocaleString()} + 介護保険 ¥${_bCare.toLocaleString()} + 子ども・子育て支援金 ¥${_bChild.toLocaleString()} = <strong>¥${_bHealthCareChild.toLocaleString()}</strong><br>
|
||||
¥${_bHealthCareChild.toLocaleString()}(上記合計) + 厚生年金 ¥${_bPension.toLocaleString()}${' '}${_bEmp > 0 ? `+ 雇用保険 ¥${_bEmp.toLocaleString()}` : ''}= <strong>社会保険小計 ¥${_bSocialSub.toLocaleString()}</strong>
|
||||
¥${_bHealthCareChild.toLocaleString()}(上記合計) + 厚生年金 ¥${_bPension.toLocaleString()}${" "}${_bEmp > 0 ? `+ 雇用保険 ¥${_bEmp.toLocaleString()}` : ""}= <strong>社会保険小計 ¥${_bSocialSub.toLocaleString()}</strong>
|
||||
</div>
|
||||
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_bSocialSub.toLocaleString()}</span></div>
|
||||
<div class="detail-row">
|
||||
@@ -1964,7 +1969,12 @@
|
||||
document.getElementById("bonusDetail").style.display = "block";
|
||||
// モバイルでは明細パネルへ自動スクロール
|
||||
if (window.innerWidth <= 767 && splitRightBonus) {
|
||||
setTimeout(function() { splitRightBonus.scrollIntoView({ behavior: "smooth", block: "start" }); }, 50);
|
||||
setTimeout(function () {
|
||||
splitRightBonus.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("賞与明細の取得に失敗しました");
|
||||
@@ -2638,19 +2648,33 @@
|
||||
|
||||
// CSVデータを生成
|
||||
let csvContent =
|
||||
"データ,従業員コード,従業員名,年月,支給額,控除額,差引支給額\n";
|
||||
"データ,従業員コード,従業員名,年月,支給額,社会保険,所得税,控除額合計,差引支給額\n";
|
||||
|
||||
// 給与データ
|
||||
if (result.data.salary) {
|
||||
(result.data.salary || []).forEach((salary) => {
|
||||
csvContent += `給与,${salary.employee_code},${salary.employee_name},${salary.payroll_year}-${String(salary.payroll_month).padStart(2, "0")},${salary.total_payment},${salary.total_deduction},${salary.net_payment}\n`;
|
||||
const socialIns =
|
||||
Number(salary.health_insurance || 0) +
|
||||
Number(salary.care_insurance || 0) +
|
||||
Number(salary.pension_insurance || 0) +
|
||||
Number(salary.employment_insurance || 0) +
|
||||
Number(salary.child_support || 0);
|
||||
const incomeTax = Number(salary.income_tax || 0);
|
||||
csvContent += `給与,${salary.employee_code},${salary.employee_name},${salary.payroll_year}-${String(salary.payroll_month).padStart(2, "0")},${salary.total_payment},${socialIns},${incomeTax},${salary.total_deduction},${salary.net_payment}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// 賞与データ
|
||||
if (result.data.bonus) {
|
||||
(result.data.bonus || []).forEach((bonus) => {
|
||||
csvContent += `賞与,${bonus.employee_code},${bonus.employee_name},${bonus.bonus_year}-${String(bonus.bonus_month).padStart(2, "0")},${bonus.total_bonus},${bonus.total_deduction},${bonus.net_bonus}\n`;
|
||||
const socialIns =
|
||||
Number(bonus.health_insurance || 0) +
|
||||
Number(bonus.care_insurance || 0) +
|
||||
Number(bonus.pension_insurance || 0) +
|
||||
Number(bonus.employment_insurance || 0) +
|
||||
Number(bonus.child_support || 0);
|
||||
const incomeTax = Number(bonus.income_tax || 0);
|
||||
csvContent += `賞与,${bonus.employee_code},${bonus.employee_name},${bonus.bonus_year}-${String(bonus.bonus_month).padStart(2, "0")},${bonus.total_bonus},${socialIns},${incomeTax},${bonus.total_deduction},${bonus.net_bonus}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -292,6 +292,20 @@
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<label for="fiscalYear">年度:</label>
|
||||
<select
|
||||
id="fiscalYear"
|
||||
style="
|
||||
padding: 4px 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
min-width: 200px;
|
||||
"
|
||||
>
|
||||
<option value="">-- 選択してください --</option>
|
||||
</select>
|
||||
|
||||
<label for="dateFrom">開始年月日:</label>
|
||||
<input type="date" id="dateFrom" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user