Files
njts-accounting-core/backend/app/static/payroll-settings.html
2026-01-20 11:15:49 +09:00

327 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>給与管理 - 設定</title>
<link rel="stylesheet" href="/css/style.css">
<style>
.nav-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
border-bottom: 2px solid #ddd;
}
.nav-tab {
padding: 10px 20px;
cursor: pointer;
border: none;
background: #f0f0f0;
border-radius: 5px 5px 0 0;
}
.nav-tab.active {
background: #007bff;
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.btn {
padding: 10px 20px;
margin-right: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
table th, table td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
table th {
background: #f0f0f0;
}
</style>
</head>
<body>
<div class="container">
<h1>給与管理 - 設定</h1>
<div class="nav-tabs">
<button class="nav-tab active" onclick="showTab('insurance')">保険料率</button>
<button class="nav-tab" onclick="showTab('tax')">所得税率表</button>
</div>
<!-- 保険料率タブ -->
<div id="tab-insurance" class="tab-content active">
<h2>社会保険料率設定</h2>
<button class="btn btn-primary" onclick="showInsuranceForm()">新規追加</button>
<div id="insuranceList"></div>
<div id="insuranceForm" style="display:none; margin-top:20px; padding:20px; border:2px solid #007bff; border-radius:5px;">
<h3>保険料率の登録</h3>
<form onsubmit="saveInsuranceRate(event)">
<div class="form-group">
<label>保険種類 *</label>
<select name="rate_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>
<input type="number" name="employee_rate" step="0.0001" required placeholder="例: 5.0 (5%)">
</div>
<div class="form-group">
<label>会社負担率 (%) *</label>
<input type="number" name="employer_rate" step="0.0001" required placeholder="例: 5.0 (5%)">
</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">
</div>
<div class="form-group">
<label>備考</label>
<input type="text" name="notes">
</div>
<button type="submit" class="btn btn-primary">登録</button>
<button type="button" class="btn btn-secondary" onclick="hideInsuranceForm()">キャンセル</button>
</form>
</div>
</div>
<!-- 所得税率表タブ -->
<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()">CSVインポート</button>
<input type="file" id="csvFile" accept=".csv" style="display:none;" onchange="importTaxTable(event)">
<div id="taxList"></div>
</div>
</div>
<script>
const API_BASE = 'http://localhost:8000';
function showTab(tabName) {
document.querySelectorAll('.nav-tab').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
if (tabName === 'insurance') {
document.querySelectorAll('.nav-tab')[0].classList.add('active');
document.getElementById('tab-insurance').classList.add('active');
loadInsuranceRates();
} else if (tabName === 'tax') {
document.querySelectorAll('.nav-tab')[1].classList.add('active');
document.getElementById('tab-tax').classList.add('active');
loadIncomeTax();
}
}
function showInsuranceForm() {
document.getElementById('insuranceForm').style.display = 'block';
}
function hideInsuranceForm() {
document.getElementById('insuranceForm').style.display = 'none';
document.querySelector('#insuranceForm form').reset();
}
async function loadInsuranceRates() {
try {
const response = await fetch(`${API_BASE}/payroll/settings/insurance-rates`);
const rates = await response.json();
const html = `
<table>
<thead>
<tr>
<th>保険種類</th>
<th>従業員負担率</th>
<th>会社負担率</th>
<th>適用期間</th>
<th>備考</th>
</tr>
</thead>
<tbody>
${rates.map(rate => `
<tr>
<td>${rate.rate_type}</td>
<td>${(parseFloat(rate.employee_rate) * 100).toFixed(2)}%</td>
<td>${(parseFloat(rate.employer_rate) * 100).toFixed(2)}%</td>
<td>${rate.valid_from} ${rate.valid_to || '現在'}</td>
<td>${rate.notes || '-'}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
document.getElementById('insuranceList').innerHTML = html;
} catch (error) {
alert('保険料率の取得に失敗しました');
console.error(error);
}
}
async function saveInsuranceRate(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
// パーセントを小数に変換
data.employee_rate = parseFloat(data.employee_rate) / 100;
data.employer_rate = parseFloat(data.employer_rate) / 100;
// 空の値を除外
if (!data.valid_to) delete data.valid_to;
if (!data.notes) delete data.notes;
try {
const response = await fetch(`${API_BASE}/payroll/settings/insurance-rates`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
if (response.ok) {
alert('保険料率を登録しました');
hideInsuranceForm();
loadInsuranceRates();
} else {
const error = await response.json();
alert('登録に失敗しました: ' + error.detail);
}
} catch (error) {
alert('登録に失敗しました');
console.error(error);
}
}
async function loadIncomeTax() {
const dependentsFilter = document.getElementById('filterDependents').value;
const params = dependentsFilter ? `?dependents_count=${dependentsFilter}` : '';
try {
const response = await fetch(`${API_BASE}/payroll/settings/income-tax${params}`);
const taxes = await response.json();
const html = `
<table>
<thead>
<tr>
<th>扶養人数</th>
<th>月収From</th>
<th>月収To</th>
<th>税額</th>
<th>適用期間</th>
</tr>
</thead>
<tbody>
${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>
`).join('')}
</tbody>
</table>
`;
document.getElementById('taxList').innerHTML = html;
} catch (error) {
alert('所得税率表の取得に失敗しました');
console.error(error);
}
}
async function importTaxTable(event) {
const file = event.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
const validFrom = prompt('適用開始日を入力してください (YYYY-MM-DD):', new Date().toISOString().split('T')[0]);
if (!validFrom) return;
try {
const response = await fetch(`${API_BASE}/payroll/settings/income-tax/import?valid_from=${validFrom}`, {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
alert(result.message);
loadIncomeTax();
} else {
const error = await response.json();
alert('インポートに失敗しました: ' + error.detail);
}
} catch (error) {
alert('インポートに失敗しました');
console.error(error);
}
event.target.value = '';
}
// 初期化
loadInsuranceRates();
</script>
</body>
</html>