修正
This commit is contained in:
@@ -1,186 +1,236 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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">
|
||||
<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;
|
||||
}
|
||||
.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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>給与管理 - 設定</h1>
|
||||
<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 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="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
|
||||
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>
|
||||
|
||||
<!-- 所得税率表タブ -->
|
||||
<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 class="form-group">
|
||||
<label>従業員負担率 (%) *</label>
|
||||
<input
|
||||
type="number"
|
||||
name="employee_rate"
|
||||
step="0.0001"
|
||||
required
|
||||
placeholder="例: 5.0 (5%)"
|
||||
/>
|
||||
</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 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';
|
||||
const API_BASE = "";
|
||||
|
||||
function showTab(tabName) {
|
||||
document.querySelectorAll('.nav-tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
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();
|
||||
}
|
||||
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 showInsuranceForm() {
|
||||
document.getElementById("insuranceForm").style.display = "block";
|
||||
}
|
||||
|
||||
function hideInsuranceForm() {
|
||||
document.getElementById('insuranceForm').style.display = 'none';
|
||||
document.querySelector('#insuranceForm form').reset();
|
||||
}
|
||||
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();
|
||||
async function loadInsuranceRates() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/insurance-rates`,
|
||||
);
|
||||
const rates = await response.json();
|
||||
|
||||
const html = `
|
||||
const html = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -192,70 +242,82 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rates.map(rate => `
|
||||
${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>
|
||||
<td>${rate.valid_from} ~ ${rate.valid_to || "現在"}</td>
|
||||
<td>${rate.notes || "-"}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('insuranceList').innerHTML = html;
|
||||
} catch (error) {
|
||||
alert('保険料率の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
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());
|
||||
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;
|
||||
// パーセントを小数に変換
|
||||
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;
|
||||
// 空の値を除外
|
||||
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)
|
||||
});
|
||||
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);
|
||||
}
|
||||
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}` : '';
|
||||
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();
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/income-tax${params}`,
|
||||
);
|
||||
const taxes = await response.json();
|
||||
|
||||
const html = `
|
||||
const html = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -267,60 +329,70 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${taxes.map(tax => `
|
||||
${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>
|
||||
<td>${tax.valid_from} ~ ${tax.valid_to || "現在"}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('taxList').innerHTML = html;
|
||||
} catch (error) {
|
||||
alert('所得税率表の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
async function importTaxTable(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
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();
|
||||
// 初期化
|
||||
loadInsuranceRates();
|
||||
</script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user