This commit is contained in:
admin
2026-01-21 10:58:19 +09:00
parent 8a00de8f03
commit 86020ada5c
36 changed files with 3417 additions and 363 deletions

View File

@@ -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;