修正
This commit is contained in:
@@ -10,9 +10,9 @@
|
||||
|
||||
<label>
|
||||
期間:
|
||||
<input type="date" id="from" />
|
||||
<input type="date" id="from" min="1900-01-01" max="2099-12-31" />
|
||||
〜
|
||||
<input type="date" id="to" />
|
||||
<input type="date" id="to" min="1900-01-01" max="2099-12-31" />
|
||||
</label>
|
||||
<button onclick="loadTrialBalance()">表示</button>
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<h2>修正仕訳入力</h2>
|
||||
|
||||
<label>仕訳日</label>
|
||||
<input type="date" id="entryDate" min="1900-01-01" max="9999-12-31" />
|
||||
<input type="date" id="entryDate" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
<label>摘要</label>
|
||||
<input type="text" id="description" style="width: 60%" />
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
type="date"
|
||||
id="entryDate"
|
||||
min="1900-01-01"
|
||||
max="2999-12-31"
|
||||
max="2099-12-31"
|
||||
onchange="onEntryDateChanged()"
|
||||
/>
|
||||
</div>
|
||||
@@ -264,10 +264,10 @@
|
||||
type="date"
|
||||
id="searchFromDate"
|
||||
min="1900-01-01"
|
||||
max="2999-12-31"
|
||||
max="2099-12-31"
|
||||
/>
|
||||
~
|
||||
<input type="date" id="searchToDate" min="1900-01-01" max="2999-12-31" />
|
||||
<input type="date" id="searchToDate" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
<label style="margin-left: 16px">摘要</label>
|
||||
<input type="text" id="searchKeyword" style="min-width: 600px" />
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
<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" />
|
||||
<input type="date" id="fromDate" min="1900-01-01" max="2099-12-31" /> ~
|
||||
<input type="date" id="toDate" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
<label>摘要</label>
|
||||
<input type="text" id="keyword" />
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<label>
|
||||
日付:
|
||||
<input type="date" id="journal-date" />
|
||||
<input type="date" id="journal-date" min="1900-01-01" max="2099-12-31" />
|
||||
</label>
|
||||
|
||||
<br /><br />
|
||||
|
||||
@@ -78,9 +78,9 @@
|
||||
<select id="accountSelect"></select>
|
||||
|
||||
<label>期間:</label>
|
||||
<input type="date" id="fromDate" />
|
||||
<input type="date" id="fromDate" min="1900-01-01" max="2099-12-31" />
|
||||
〜
|
||||
<input type="date" id="toDate" />
|
||||
<input type="date" id="toDate" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
<button onclick="reloadTransactions()">表示</button>
|
||||
</div>
|
||||
|
||||
@@ -108,10 +108,22 @@
|
||||
font-size: 1.2em;
|
||||
color: #007bff;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a>
|
||||
|
||||
<h1>給与管理 - 月次給与計算</h1>
|
||||
|
||||
<div class="filters">
|
||||
@@ -188,7 +200,13 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>支給日 *</label>
|
||||
<input type="date" name="payment_date" required />
|
||||
<input
|
||||
type="date"
|
||||
name="payment_date"
|
||||
required
|
||||
min="1900-01-01"
|
||||
max="2099-12-31"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -292,7 +310,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = "http://localhost:8000";
|
||||
const API_BASE = "http://localhost:18080";
|
||||
|
||||
async function loadEmployees() {
|
||||
const response = await fetch(
|
||||
@@ -436,19 +454,59 @@
|
||||
);
|
||||
const payroll = await response.json();
|
||||
|
||||
// 従業員情報と扶養家族情報を取得
|
||||
const empResponse = await fetch(
|
||||
`${API_BASE}/payroll/employees/${payroll.employee_id}`
|
||||
);
|
||||
const employee = await empResponse.json();
|
||||
|
||||
// 源泉税控除対象人数を計算(19歳以上のみ)
|
||||
const dependentCount = employee.dependents
|
||||
? employee.dependents.filter((d) => {
|
||||
// 有効期間チェック
|
||||
if (d.valid_to && new Date(d.valid_to) < new Date())
|
||||
return false;
|
||||
|
||||
// 生年月日から年齢を計算(その年の12月31日時点)
|
||||
if (!d.birth_date) return false;
|
||||
const birthDate = new Date(d.birth_date);
|
||||
const targetYear = payroll.payroll_year;
|
||||
const yearEnd = new Date(targetYear, 11, 31); // 12月31日
|
||||
let age = yearEnd.getFullYear() - birthDate.getFullYear();
|
||||
const monthDiff = yearEnd.getMonth() - birthDate.getMonth();
|
||||
if (
|
||||
monthDiff < 0 ||
|
||||
(monthDiff === 0 && yearEnd.getDate() < birthDate.getDate())
|
||||
) {
|
||||
age--;
|
||||
}
|
||||
|
||||
// 19歳以上のみ源泉税控除対象(16-18歳は住民税のみ)
|
||||
return age >= 19;
|
||||
}).length
|
||||
: 0;
|
||||
|
||||
const html = `
|
||||
<h2>${payroll.payroll_year}年${
|
||||
payroll.payroll_month
|
||||
}月 給与明細</h2>
|
||||
<p>従業員ID: ${payroll.employee_id} | 支給日: ${
|
||||
<p><strong>従業員:</strong> ${employee.employee_code} - ${
|
||||
employee.name
|
||||
} | <strong>支給日:</strong> ${
|
||||
payroll.payment_date
|
||||
}</p>
|
||||
} | <strong>甲欄扶養親族人数(源泉税控除対象):</strong> ${dependentCount}人</p>
|
||||
<p>ステータス: <span class="status-badge status-${
|
||||
payroll.status
|
||||
}">${getStatusLabel(payroll.status)}</span></p>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>勤怠情報</h3>
|
||||
<h3>勤怠情報 ${
|
||||
payroll.status === "calculated"
|
||||
? '<button class="btn btn-primary" onclick="editPayroll(' +
|
||||
payroll.payroll_id +
|
||||
')" style="padding: 5px 10px; font-size: 12px; float: right;">編集</button>'
|
||||
: ""
|
||||
}</h3>
|
||||
<div class="detail-row"><span>出勤日数:</span><span>${
|
||||
payroll.working_days
|
||||
}日</span></div>
|
||||
@@ -493,15 +551,27 @@
|
||||
<div class="detail-row"><span>健康保険:</span><span>¥${Number(
|
||||
payroll.health_insurance
|
||||
).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>介護保険:</span><span>¥${Number(
|
||||
payroll.care_insurance || 0
|
||||
).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>厚生年金:</span><span>¥${Number(
|
||||
payroll.pension_insurance
|
||||
).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>雇用保険:</span><span>¥${Number(
|
||||
payroll.employment_insurance
|
||||
).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>所得税:</span><span>¥${Number(
|
||||
payroll.income_tax
|
||||
).toLocaleString()}</span></div>
|
||||
<div class="detail-row">
|
||||
<span>所得税 <small style="color:#666;">(甲欄${dependentCount}人)</small>:</span>
|
||||
<span>¥${Number(
|
||||
payroll.income_tax
|
||||
).toLocaleString()}</span>
|
||||
</div>
|
||||
<div style="padding: 5px 10px; background: #f0f8ff; border-radius: 3px; margin: 5px 0; font-size: 12px; color: #666;">
|
||||
<small>
|
||||
※課税対象額から源泉徴収税額表(甲欄)を参照して計算<br>
|
||||
扶養親族${dependentCount}人(19歳以上のみ対象)
|
||||
</small>
|
||||
</div>
|
||||
<div class="detail-row"><span>住民税:</span><span>¥${Number(
|
||||
payroll.resident_tax
|
||||
).toLocaleString()}</span></div>
|
||||
@@ -544,6 +614,119 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function editPayroll(payrollId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/calculation/${payrollId}`
|
||||
);
|
||||
const payroll = await response.json();
|
||||
|
||||
// 編集フォームを表示
|
||||
const html = `
|
||||
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 800px; max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000;">
|
||||
<h2>給与データ編集</h2>
|
||||
<form id="editPayrollForm" onsubmit="savePayrollEdit(event, ${payrollId})">
|
||||
<div class="detail-section">
|
||||
<h3>勤怠情報</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>出勤日数</label>
|
||||
<input type="number" name="working_days" step="0.5" value="${payroll.working_days}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>勤務時間</label>
|
||||
<input type="number" name="working_hours" step="0.5" value="${payroll.working_hours}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>残業時間</label>
|
||||
<input type="number" name="overtime_hours" step="0.5" value="${payroll.overtime_hours}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>休日出勤時間</label>
|
||||
<input type="number" name="holiday_hours" step="0.5" value="${payroll.holiday_hours}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>遅刻時間</label>
|
||||
<input type="number" name="late_hours" step="0.5" value="${payroll.late_hours}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>欠勤日数</label>
|
||||
<input type="number" name="absent_days" step="0.5" value="${payroll.absent_days}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>その他</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>その他手当</label>
|
||||
<input type="number" name="other_allowance" step="0.01" value="${payroll.other_allowance}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>住民税</label>
|
||||
<input type="number" name="resident_tax" step="0.01" value="${payroll.resident_tax}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>その他控除</label>
|
||||
<input type="number" name="other_deduction" step="0.01" value="${payroll.other_deduction}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">保存して再計算</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="this.closest('div').remove()">キャンセル</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const editDiv = document.createElement("div");
|
||||
editDiv.innerHTML = html;
|
||||
document.body.appendChild(editDiv);
|
||||
} catch (error) {
|
||||
alert("給与データの取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function savePayrollEdit(event, payrollId) {
|
||||
event.preventDefault();
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
const data = {};
|
||||
|
||||
formData.forEach((value, key) => {
|
||||
data[key] = isNaN(value) || value === "" ? value : Number(value);
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/calculation/${payrollId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
alert("給与データを更新しました");
|
||||
form.closest("div").remove();
|
||||
|
||||
// 再計算
|
||||
await recalculatePayroll(payrollId);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("更新に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("更新に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function approvePayroll(payrollId) {
|
||||
if (!confirm("この給与を承認しますか?")) return;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -56,311 +56,374 @@
|
||||
table th {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a>
|
||||
|
||||
<h1>給与管理 - 給与設定</h1>
|
||||
|
||||
<div class="employee-select">
|
||||
<label><strong>従業員を選択:</strong></label>
|
||||
<select id="employeeSelect" onchange="loadSalarySettings()">
|
||||
<option value="">従業員を選択してください</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="showAddForm()">
|
||||
新規設定追加
|
||||
</button>
|
||||
|
||||
<div id="settingsContent" style="display: none">
|
||||
<h2>給与設定履歴</h2>
|
||||
<button class="btn btn-primary" onclick="showAddForm()">
|
||||
新規設定追加
|
||||
</button>
|
||||
<div id="settingsList"></div>
|
||||
<div id="settingsList" style="margin-top: 20px"></div>
|
||||
|
||||
<div
|
||||
id="addForm"
|
||||
style="
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid #007bff;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<h3>給与設定の追加</h3>
|
||||
<form onsubmit="saveSalarySetting(event)">
|
||||
<div class="form-group">
|
||||
<label>基本給 (円) *</label>
|
||||
<input type="number" name="base_salary" step="0.01" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>時給 (円)</label>
|
||||
<input type="number" name="hourly_rate" step="0.01" />
|
||||
<small>時給制の場合に入力してください</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>雇用形態 *</label>
|
||||
<select name="employment_type" required>
|
||||
<option value="">選択してください</option>
|
||||
<option value="正社員">正社員</option>
|
||||
<option value="契約社員">契約社員</option>
|
||||
<option value="パート">パート</option>
|
||||
<option value="アルバイト">アルバイト</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>支給形態 *</label>
|
||||
<select name="payment_type" required>
|
||||
<option value="">選択してください</option>
|
||||
<option value="月給">月給</option>
|
||||
<option value="時給">時給</option>
|
||||
<option value="日給">日給</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>通勤手当 (円)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="commute_allowance"
|
||||
step="0.01"
|
||||
value="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>その他手当 (円)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="other_allowance"
|
||||
step="0.01"
|
||||
value="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用開始日 *</label>
|
||||
<input type="date" name="valid_from" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用終了日</label>
|
||||
<input type="date" name="valid_to" />
|
||||
<small>通常は空欄のままにしてください</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onclick="hideAddForm()"
|
||||
>
|
||||
キャンセル
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
id="addForm"
|
||||
style="
|
||||
display: none;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid #007bff;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<h3 id="formTitle">給与設定の追加</h3>
|
||||
<form onsubmit="saveSalarySetting(event)">
|
||||
<input type="hidden" name="setting_id" id="setting_id" />
|
||||
|
||||
<div
|
||||
id="currentSetting"
|
||||
style="
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #e7f3ff;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<h3>現在の給与設定</h3>
|
||||
<div id="currentSettingContent"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>従業員 *</label>
|
||||
<select name="employee_id" id="employee_id" required>
|
||||
<option value="">選択してください</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>基本給 (円) *</label>
|
||||
<input
|
||||
type="number"
|
||||
name="base_salary"
|
||||
id="base_salary"
|
||||
step="0.01"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>時給 (円)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="hourly_rate"
|
||||
id="hourly_rate"
|
||||
step="0.01"
|
||||
/>
|
||||
<small>時給制の場合に入力してください</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>雇用形態 *</label>
|
||||
<select name="employment_type" id="employment_type" required>
|
||||
<option value="">選択してください</option>
|
||||
<option value="正社員">正社員</option>
|
||||
<option value="契約社員">契約社員</option>
|
||||
<option value="パート">パート</option>
|
||||
<option value="アルバイト">アルバイト</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>支給形態 *</label>
|
||||
<select name="payment_type" id="payment_type" required>
|
||||
<option value="">選択してください</option>
|
||||
<option value="月給">月給</option>
|
||||
<option value="時給">時給</option>
|
||||
<option value="日給">日給</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>通勤手当 (円)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="commute_allowance"
|
||||
id="commute_allowance"
|
||||
step="0.01"
|
||||
value="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>その他手当 (円)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="other_allowance"
|
||||
id="other_allowance"
|
||||
step="0.01"
|
||||
value="0"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用開始日 *</label>
|
||||
<input
|
||||
type="date"
|
||||
name="valid_from"
|
||||
id="valid_from"
|
||||
required
|
||||
min="1900-01-01"
|
||||
max="2099-12-31"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用終了日</label>
|
||||
<input
|
||||
type="date"
|
||||
name="valid_to"
|
||||
id="valid_to"
|
||||
min="1900-01-01"
|
||||
max="2099-12-31"
|
||||
/>
|
||||
<small>通常は空欄のままにしてください</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onclick="hideAddForm()"
|
||||
>
|
||||
キャンセル
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = "http://localhost:8000";
|
||||
let currentEmployeeId = null;
|
||||
const API_BASE = "http://localhost:18080";
|
||||
let employees = [];
|
||||
|
||||
async function loadEmployees() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/employees/?is_active=true`
|
||||
);
|
||||
const employees = await response.json();
|
||||
employees = await response.json();
|
||||
|
||||
const select = document.getElementById("employeeSelect");
|
||||
const select = document.getElementById("employee_id");
|
||||
select.innerHTML =
|
||||
'<option value="">従業員を選択してください</option>' +
|
||||
'<option value="">選択してください</option>' +
|
||||
employees
|
||||
.map(
|
||||
(emp) =>
|
||||
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`
|
||||
)
|
||||
.join("");
|
||||
|
||||
loadAllSalarySettings();
|
||||
} catch (error) {
|
||||
alert("従業員一覧の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSalarySettings() {
|
||||
const employeeId = document.getElementById("employeeSelect").value;
|
||||
if (!employeeId) {
|
||||
document.getElementById("settingsContent").style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
currentEmployeeId = employeeId;
|
||||
document.getElementById("settingsContent").style.display = "block";
|
||||
|
||||
async function loadAllSalarySettings() {
|
||||
try {
|
||||
// 給与設定履歴を取得
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${employeeId}`
|
||||
);
|
||||
const settings = await response.json();
|
||||
// すべての従業員の給与設定を取得
|
||||
const allSettings = [];
|
||||
for (const emp of employees) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${emp.employee_id}/current`
|
||||
);
|
||||
if (response.ok) {
|
||||
const setting = await response.json();
|
||||
allSettings.push({
|
||||
...setting,
|
||||
employee_code: emp.employee_code,
|
||||
employee_name: emp.name,
|
||||
employee_id: emp.employee_id,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`従業員 ${emp.employee_id} の給与設定取得失敗`,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const html = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>適用期間</th>
|
||||
<th>基本給</th>
|
||||
<th>時給</th>
|
||||
<th>雇用形態</th>
|
||||
<th>支給形態</th>
|
||||
<th>通勤手当</th>
|
||||
<th>その他手当</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${settings
|
||||
.map(
|
||||
(s) => `
|
||||
<tr>
|
||||
<td>${s.valid_from} ~ ${
|
||||
s.valid_to || "現在"
|
||||
}</td>
|
||||
<td>¥${Number(
|
||||
s.base_salary
|
||||
).toLocaleString()}</td>
|
||||
<td>${
|
||||
s.hourly_rate
|
||||
? "¥" +
|
||||
Number(s.hourly_rate).toLocaleString()
|
||||
: "-"
|
||||
}</td>
|
||||
<td>${s.employment_type}</td>
|
||||
<td>${s.payment_type}</td>
|
||||
<td>¥${Number(
|
||||
s.commute_allowance
|
||||
).toLocaleString()}</td>
|
||||
<td>¥${Number(
|
||||
s.other_allowance
|
||||
).toLocaleString()}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById("settingsList").innerHTML = html;
|
||||
|
||||
// 現在の設定を取得
|
||||
loadCurrentSetting(employeeId);
|
||||
displayAllSettings(allSettings);
|
||||
} catch (error) {
|
||||
alert("給与設定の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCurrentSetting(employeeId) {
|
||||
function displayAllSettings(settings) {
|
||||
const html = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>従業員コード</th>
|
||||
<th>氏名</th>
|
||||
<th>基本給</th>
|
||||
<th>時給</th>
|
||||
<th>雇用形態</th>
|
||||
<th>支給形態</th>
|
||||
<th>通勤手当</th>
|
||||
<th>その他手当</th>
|
||||
<th>適用期間</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${
|
||||
settings.length > 0
|
||||
? settings
|
||||
.map(
|
||||
(s) => `
|
||||
<tr>
|
||||
<td>${s.employee_code}</td>
|
||||
<td>${s.employee_name}</td>
|
||||
<td>¥${Number(s.base_salary).toLocaleString()}</td>
|
||||
<td>${
|
||||
s.hourly_rate
|
||||
? "¥" + Number(s.hourly_rate).toLocaleString()
|
||||
: "-"
|
||||
}</td>
|
||||
<td>${s.employment_type}</td>
|
||||
<td>${s.payment_type}</td>
|
||||
<td>¥${Number(s.commute_allowance).toLocaleString()}</td>
|
||||
<td>¥${Number(s.other_allowance).toLocaleString()}</td>
|
||||
<td>${s.valid_from} ~ ${s.valid_to || "現在"}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" onclick="editSetting(${
|
||||
s.setting_id
|
||||
})" style="padding: 5px 10px; font-size: 12px;">編集</button>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("")
|
||||
: '<tr><td colspan="10" style="text-align:center;">給与設定が登録されていません</td></tr>'
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById("settingsList").innerHTML = html;
|
||||
}
|
||||
|
||||
async function editSetting(settingId) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${employeeId}/current`
|
||||
);
|
||||
if (response.ok) {
|
||||
const setting = await response.json();
|
||||
// 設定IDから詳細を取得する必要がありますが、現在のAPIでは難しいので
|
||||
// 全従業員の設定を取得して該当するものを探す
|
||||
for (const emp of employees) {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${emp.employee_id}`
|
||||
);
|
||||
const settings = await response.json();
|
||||
const setting = settings.find((s) => s.setting_id === settingId);
|
||||
|
||||
const html = `
|
||||
<div style="display:grid; grid-template-columns: repeat(2, 1fr); gap:15px;">
|
||||
<div><strong>基本給:</strong> ¥${Number(
|
||||
setting.base_salary
|
||||
).toLocaleString()}</div>
|
||||
<div><strong>時給:</strong> ${
|
||||
setting.hourly_rate
|
||||
? "¥" +
|
||||
Number(setting.hourly_rate).toLocaleString()
|
||||
: "-"
|
||||
}</div>
|
||||
<div><strong>雇用形態:</strong> ${
|
||||
setting.employment_type
|
||||
}</div>
|
||||
<div><strong>支給形態:</strong> ${
|
||||
setting.payment_type
|
||||
}</div>
|
||||
<div><strong>通勤手当:</strong> ¥${Number(
|
||||
setting.commute_allowance
|
||||
).toLocaleString()}</div>
|
||||
<div><strong>その他手当:</strong> ¥${Number(
|
||||
setting.other_allowance
|
||||
).toLocaleString()}</div>
|
||||
<div><strong>適用期間:</strong> ${
|
||||
setting.valid_from
|
||||
} ~ ${setting.valid_to || "現在"}</div>
|
||||
</div>
|
||||
`;
|
||||
if (setting) {
|
||||
// フォームに値を設定
|
||||
document.getElementById("formTitle").textContent =
|
||||
"給与設定の編集";
|
||||
document.getElementById("setting_id").value = setting.setting_id;
|
||||
document.getElementById("employee_id").value = emp.employee_id;
|
||||
document.getElementById("employee_id").disabled = true; // 従業員は変更不可
|
||||
document.getElementById("base_salary").value =
|
||||
setting.base_salary;
|
||||
document.getElementById("hourly_rate").value =
|
||||
setting.hourly_rate || "";
|
||||
document.getElementById("employment_type").value =
|
||||
setting.employment_type;
|
||||
document.getElementById("payment_type").value =
|
||||
setting.payment_type;
|
||||
document.getElementById("commute_allowance").value =
|
||||
setting.commute_allowance;
|
||||
document.getElementById("other_allowance").value =
|
||||
setting.other_allowance;
|
||||
document.getElementById("valid_from").value = setting.valid_from;
|
||||
document.getElementById("valid_to").value =
|
||||
setting.valid_to || "";
|
||||
|
||||
document.getElementById("currentSettingContent").innerHTML = html;
|
||||
} else {
|
||||
document.getElementById("currentSettingContent").innerHTML =
|
||||
"<p>給与設定が登録されていません</p>";
|
||||
document.getElementById("addForm").style.display = "block";
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById("currentSettingContent").innerHTML =
|
||||
"<p>給与設定の取得に失敗しました</p>";
|
||||
alert("給与設定の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function showAddForm() {
|
||||
document.getElementById("formTitle").textContent = "給与設定の追加";
|
||||
document.getElementById("addForm").style.display = "block";
|
||||
document.querySelector("#addForm form").reset();
|
||||
document.getElementById("setting_id").value = "";
|
||||
document.getElementById("employee_id").disabled = false;
|
||||
// デフォルト値設定
|
||||
document.querySelector('[name="valid_from"]').value = new Date()
|
||||
document.getElementById("valid_from").value = new Date()
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
document.getElementById("commute_allowance").value = "0";
|
||||
document.getElementById("other_allowance").value = "0";
|
||||
}
|
||||
|
||||
function hideAddForm() {
|
||||
document.getElementById("addForm").style.display = "none";
|
||||
document.querySelector("#addForm form").reset();
|
||||
document.getElementById("employee_id").disabled = false;
|
||||
}
|
||||
|
||||
async function saveSalarySetting(event) {
|
||||
event.preventDefault();
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
const settingId = document.getElementById("setting_id").value;
|
||||
|
||||
data.employee_id = parseInt(currentEmployeeId);
|
||||
data.base_salary = parseFloat(data.base_salary) || 0;
|
||||
data.commute_allowance = parseFloat(data.commute_allowance) || 0;
|
||||
data.other_allowance = parseFloat(data.other_allowance) || 0;
|
||||
const data = {
|
||||
employee_id: parseInt(formData.get("employee_id")),
|
||||
base_salary: parseFloat(formData.get("base_salary")) || 0,
|
||||
employment_type: formData.get("employment_type"),
|
||||
payment_type: formData.get("payment_type"),
|
||||
commute_allowance: parseFloat(formData.get("commute_allowance")) || 0,
|
||||
other_allowance: parseFloat(formData.get("other_allowance")) || 0,
|
||||
valid_from: formData.get("valid_from"),
|
||||
};
|
||||
|
||||
if (data.hourly_rate) {
|
||||
data.hourly_rate = parseFloat(data.hourly_rate);
|
||||
} else {
|
||||
delete data.hourly_rate;
|
||||
if (formData.get("hourly_rate")) {
|
||||
data.hourly_rate = parseFloat(formData.get("hourly_rate"));
|
||||
}
|
||||
|
||||
if (!data.valid_to) {
|
||||
delete data.valid_to;
|
||||
if (formData.get("valid_to")) {
|
||||
data.valid_to = formData.get("valid_to");
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/salary`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
let response;
|
||||
if (settingId) {
|
||||
// 更新
|
||||
response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${settingId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 新規追加
|
||||
response = await fetch(`${API_BASE}/payroll/settings/salary`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
alert("給与設定を保存しました");
|
||||
alert(
|
||||
settingId ? "給与設定を更新しました" : "給与設定を保存しました"
|
||||
);
|
||||
hideAddForm();
|
||||
loadSalarySettings();
|
||||
loadAllSalarySettings();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("保存に失敗しました: " + error.detail);
|
||||
|
||||
@@ -114,10 +114,57 @@
|
||||
border-left: 4px solid #007bff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.year-item {
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.year-item:hover {
|
||||
background: #e8f4ff;
|
||||
}
|
||||
.year-item.expanded {
|
||||
background: #e8f4ff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
.year-header {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.year-data {
|
||||
display: none;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.year-item.expanded .year-data {
|
||||
display: block;
|
||||
}
|
||||
.filter-controls {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a>
|
||||
|
||||
<h1>給与管理 - 設定</h1>
|
||||
|
||||
<div class="nav-tabs">
|
||||
@@ -129,6 +176,9 @@
|
||||
</button>
|
||||
<button class="nav-tab" onclick="showTab('limits')">上限設定</button>
|
||||
<button class="nav-tab" onclick="showTab('tax')">所得税率表</button>
|
||||
<button class="nav-tab" onclick="showTab('standard-remuneration')">
|
||||
標準報酬月額表
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 保険料率タブ -->
|
||||
@@ -459,11 +509,22 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>適用開始日 *</label>
|
||||
<input type="date" name="valid_from" required />
|
||||
<input
|
||||
type="date"
|
||||
name="valid_from"
|
||||
required
|
||||
min="1900-01-01"
|
||||
max="2099-12-31"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用終了日</label>
|
||||
<input type="date" name="valid_to" />
|
||||
<input
|
||||
type="date"
|
||||
name="valid_to"
|
||||
min="1900-01-01"
|
||||
max="2099-12-31"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -487,18 +548,6 @@
|
||||
<!-- 所得税率表タブ -->
|
||||
<div id="tab-tax" class="tab-content">
|
||||
<h2>所得税率表</h2>
|
||||
<div class="form-group" style="max-width: 300px">
|
||||
<label>扶養人数でフィルター</label>
|
||||
<select id="filterDependents" onchange="loadIncomeTax()">
|
||||
<option value="">全て</option>
|
||||
<option value="0">0人</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>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="document.getElementById('csvFile').click()"
|
||||
@@ -512,7 +561,40 @@
|
||||
style="display: none"
|
||||
onchange="importTaxTable(event)"
|
||||
/>
|
||||
<div id="taxList"></div>
|
||||
<div id="taxYearsList" style="margin-top: 20px"></div>
|
||||
<div id="taxDataContainer" style="margin-top: 20px"></div>
|
||||
</div>
|
||||
|
||||
<!-- 標準報酬月額表タブ -->
|
||||
<div id="tab-standard-remuneration" class="tab-content">
|
||||
<h2>標準報酬月額表</h2>
|
||||
<div class="info-box">
|
||||
<strong>標準報酬月額表について:</strong>
|
||||
<ul style="margin: 10px 0 0 20px">
|
||||
<li>健康保険・厚生年金保険の保険料額表をインポートします</li>
|
||||
<li>都道府県ごとの料率表に対応しています</li>
|
||||
<li>年度と都道府県を指定してください</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="margin: 20px 0">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="document.getElementById('stdRemunerationFile').click()"
|
||||
>
|
||||
Excel/CSVインポート
|
||||
</button>
|
||||
<input
|
||||
type="file"
|
||||
id="stdRemunerationFile"
|
||||
accept=".csv,.xlsx,.xls"
|
||||
style="display: none"
|
||||
onchange="importStdRemunerationTable(event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="stdRemunerationYearsList" style="margin-top: 20px"></div>
|
||||
<div id="stdRemunerationDataContainer" style="margin-top: 20px"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -535,6 +617,7 @@
|
||||
"child-support": 1,
|
||||
limits: 2,
|
||||
tax: 3,
|
||||
"standard-remuneration": 4,
|
||||
};
|
||||
|
||||
document
|
||||
@@ -551,6 +634,8 @@
|
||||
loadLimitSettings();
|
||||
} else if (tabName === "tax") {
|
||||
loadIncomeTax();
|
||||
} else if (tabName === "standard-remuneration") {
|
||||
loadStdRemunerationData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,21 +1274,95 @@
|
||||
// =====================================================
|
||||
// 所得税率表関連
|
||||
// =====================================================
|
||||
let currentExpandedYear = null;
|
||||
|
||||
async function loadIncomeTax() {
|
||||
const dependentsFilter =
|
||||
document.getElementById("filterDependents").value;
|
||||
const params = dependentsFilter
|
||||
? `?dependents_count=${dependentsFilter}`
|
||||
: "";
|
||||
await loadIncomeTaxYears();
|
||||
}
|
||||
|
||||
async function loadIncomeTaxYears() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/income-tax/years`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.years || data.years.length === 0) {
|
||||
document.getElementById("taxYearsList").innerHTML =
|
||||
"<p>データがありません。</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
const html = data.years
|
||||
.map(
|
||||
(year) => `
|
||||
<div class="year-item" id="year-${year}" onclick="toggleYear(${year})">
|
||||
<div class="year-header">
|
||||
📅 ${year}年度 <span style="font-size: 14px; color: #666;">(クリックして展開)</span>
|
||||
</div>
|
||||
<div class="year-data" id="data-${year}">
|
||||
<div class="filter-controls">
|
||||
<label>扶養人数でフィルター: </label>
|
||||
<select id="filter-${year}" onchange="loadYearData(${year})">
|
||||
<option value="">全て</option>
|
||||
<option value="0">0人</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>
|
||||
</select>
|
||||
</div>
|
||||
<div id="table-${year}">読み込み中...</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
document.getElementById("taxYearsList").innerHTML = html;
|
||||
} catch (error) {
|
||||
alert("年度リストの取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleYear(year) {
|
||||
const yearItem = document.getElementById(`year-${year}`);
|
||||
const wasExpanded = yearItem.classList.contains("expanded");
|
||||
|
||||
// 他の年度を閉じる
|
||||
document.querySelectorAll(".year-item").forEach((item) => {
|
||||
item.classList.remove("expanded");
|
||||
});
|
||||
|
||||
if (!wasExpanded) {
|
||||
yearItem.classList.add("expanded");
|
||||
currentExpandedYear = year;
|
||||
await loadYearData(year);
|
||||
} else {
|
||||
currentExpandedYear = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadYearData(year) {
|
||||
const filterSelect = document.getElementById(`filter-${year}`);
|
||||
const dependentsFilter = filterSelect ? filterSelect.value : "";
|
||||
const params = new URLSearchParams({ tax_year: year });
|
||||
if (dependentsFilter) {
|
||||
params.append("dependents_count", dependentsFilter);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/income-tax${params}`
|
||||
`${API_BASE}/payroll/settings/income-tax?${params}`
|
||||
);
|
||||
const taxes = await response.json();
|
||||
|
||||
if (taxes.length === 0) {
|
||||
document.getElementById("taxList").innerHTML =
|
||||
document.getElementById(`table-${year}`).innerHTML =
|
||||
"<p>データがありません。</p>";
|
||||
return;
|
||||
}
|
||||
@@ -1223,27 +1382,25 @@
|
||||
${taxes
|
||||
.map(
|
||||
(tax) => `
|
||||
<tr>
|
||||
<td>${tax.dependents_count}人</td>
|
||||
<td>¥${Number(
|
||||
tax.monthly_income_from
|
||||
).toLocaleString()}</td>
|
||||
<td>¥${Number(
|
||||
tax.monthly_income_to
|
||||
).toLocaleString()}</td>
|
||||
<td>¥${Number(tax.tax_amount).toLocaleString()}</td>
|
||||
<td>${tax.valid_from} ~ ${tax.valid_to || "現在"}</td>
|
||||
</tr>
|
||||
`
|
||||
<tr>
|
||||
<td>${tax.dependents_count}人</td>
|
||||
<td>¥${Number(
|
||||
tax.monthly_income_from
|
||||
).toLocaleString()}</td>
|
||||
<td>¥${Number(tax.monthly_income_to).toLocaleString()}</td>
|
||||
<td>¥${Number(tax.tax_amount).toLocaleString()}</td>
|
||||
<td>${tax.valid_from} ~ ${tax.valid_to || "現在"}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById("taxList").innerHTML = html;
|
||||
document.getElementById(`table-${year}`).innerHTML = html;
|
||||
} catch (error) {
|
||||
alert("所得税率表の取得に失敗しました");
|
||||
alert("データの取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@@ -1274,7 +1431,7 @@
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
alert(result.message + `\n年度: ${result.year}`);
|
||||
loadIncomeTax();
|
||||
loadIncomeTaxYears();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("インポートに失敗しました: " + JSON.stringify(error.detail));
|
||||
@@ -1287,6 +1444,224 @@
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 標準報酬月額表関連の関数
|
||||
// =====================================================
|
||||
async function loadStdRemunerationData() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/standard-remuneration/years`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to load years");
|
||||
}
|
||||
const years = await response.json();
|
||||
|
||||
if (!years || years.length === 0) {
|
||||
document.getElementById("stdRemunerationYearsList").innerHTML =
|
||||
"<p>データがありません。Excelファイルをインポートしてください。</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
const html = years
|
||||
.map(
|
||||
(year) => `
|
||||
<div class="year-item" id="std-year-${year}">
|
||||
<div class="year-header" onclick="toggleStdRemunerationYear(${year})">
|
||||
<span class="year-title">${year}年度</span>
|
||||
<span class="expand-icon">▼</span>
|
||||
</div>
|
||||
<div class="year-content" id="std-content-${year}"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
|
||||
document.getElementById("stdRemunerationYearsList").innerHTML = html;
|
||||
} catch (error) {
|
||||
alert("年度リストの取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleStdRemunerationYear(year) {
|
||||
const yearItem = document.getElementById(`std-year-${year}`);
|
||||
const wasExpanded = yearItem.classList.contains("expanded");
|
||||
|
||||
// 他の年度を閉じる
|
||||
document.querySelectorAll(".year-item").forEach((item) => {
|
||||
item.classList.remove("expanded");
|
||||
});
|
||||
|
||||
if (!wasExpanded) {
|
||||
yearItem.classList.add("expanded");
|
||||
await loadStdRemunerationYearData(year);
|
||||
} else {
|
||||
document.getElementById(`std-content-${year}`).innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStdRemunerationYearData(year) {
|
||||
const filterSelect = document.getElementById(`std-filter-${year}`);
|
||||
const prefectureFilter = filterSelect ? filterSelect.value : "";
|
||||
const params = new URLSearchParams({ year: year });
|
||||
if (prefectureFilter) {
|
||||
params.append("prefecture", prefectureFilter);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/standard-remuneration?${params}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.length === 0) {
|
||||
document.getElementById(`std-content-${year}`).innerHTML =
|
||||
'<p class="no-data">データがありません</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 都道府県でグループ化
|
||||
const groupedByPrefecture = {};
|
||||
data.forEach((row) => {
|
||||
if (!groupedByPrefecture[row.prefecture]) {
|
||||
groupedByPrefecture[row.prefecture] = [];
|
||||
}
|
||||
groupedByPrefecture[row.prefecture].push(row);
|
||||
});
|
||||
|
||||
// 都道府県一覧を取得
|
||||
const prefectures = Object.keys(groupedByPrefecture).sort();
|
||||
|
||||
const html = `
|
||||
<div class="filter-section">
|
||||
<label>都道府県で絞り込み:</label>
|
||||
<select id="std-filter-${year}" onchange="loadStdRemunerationYearData(${year})" style="margin-left: 10px">
|
||||
<option value="">すべて</option>
|
||||
${prefectures
|
||||
.map(
|
||||
(pref) =>
|
||||
`<option value="${pref}" ${
|
||||
prefectureFilter === pref ? "selected" : ""
|
||||
}>${pref}</option>`
|
||||
)
|
||||
.join("")}
|
||||
</select>
|
||||
</div>
|
||||
${Object.entries(groupedByPrefecture)
|
||||
.map(
|
||||
([prefecture, rows]) => `
|
||||
<div style="margin-top: 20px">
|
||||
<h4>${prefecture}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2">等級</th>
|
||||
<th rowspan="2">標準報酬月額</th>
|
||||
<th colspan="2">報酬月額</th>
|
||||
<th colspan="2">健康保険料</th>
|
||||
<th rowspan="2">厚生年金保険料</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>以上</th>
|
||||
<th>未満</th>
|
||||
<th>介護保険なし</th>
|
||||
<th>介護保険あり</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows
|
||||
.map(
|
||||
(row) => `
|
||||
<tr>
|
||||
<td>${row.grade}</td>
|
||||
<td class="number">${formatNumber(
|
||||
row.monthly_amount
|
||||
)}</td>
|
||||
<td class="number">${formatNumber(row.salary_from)}</td>
|
||||
<td class="number">${
|
||||
row.salary_to ? formatNumber(row.salary_to) : "〜"
|
||||
}</td>
|
||||
<td class="number">${formatNumber(
|
||||
row.health_insurance_no_care
|
||||
)}</td>
|
||||
<td class="number">${formatNumber(
|
||||
row.health_insurance_with_care
|
||||
)}</td>
|
||||
<td class="number">${formatNumber(
|
||||
row.pension_insurance
|
||||
)}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
`;
|
||||
|
||||
document.getElementById(`std-content-${year}`).innerHTML = html;
|
||||
} catch (error) {
|
||||
alert("データの取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function importStdRemunerationTable(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const prefecture = prompt(
|
||||
"都道府県名を入力してください(例: 東京):",
|
||||
""
|
||||
);
|
||||
if (!prefecture || !prefecture.trim()) {
|
||||
alert("都道府県名を入力してください");
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
formData.append("prefecture", prefecture.trim());
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/standard-remuneration/import`,
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
alert(
|
||||
`インポートしました: ${result.imported_count}件\n年度: ${
|
||||
result.year
|
||||
}年\n都道府県: ${prefecture}\n\n取得元: ${result.source || ""}`
|
||||
);
|
||||
loadStdRemunerationData();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("インポートに失敗しました: " + JSON.stringify(error.detail));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("インポートに失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
function formatNumber(num) {
|
||||
if (num === null || num === undefined) return "-";
|
||||
return Number(num).toLocaleString("ja-JP");
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 初期化
|
||||
// =====================================================
|
||||
|
||||
@@ -99,10 +99,10 @@
|
||||
|
||||
<div class="search-form">
|
||||
<label for="dateFrom">開始年月日:</label>
|
||||
<input type="date" id="dateFrom" min="1900-01-01" max="9999-12-31" />
|
||||
<input type="date" id="dateFrom" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
<label for="dateTo">終了年月日:</label>
|
||||
<input type="date" id="dateTo" min="1900-01-01" max="9999-12-31" />
|
||||
<input type="date" id="dateTo" min="1900-01-01" max="2099-12-31" />
|
||||
|
||||
<button id="btnSearch">検索</button>
|
||||
<button id="btnFullYear" class="secondary">全年度表示</button>
|
||||
|
||||
Reference in New Issue
Block a user