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

379 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>
.employee-select {
margin-bottom: 20px;
padding: 15px;
background: #f9f9f9;
border-radius: 5px;
}
.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="employee-select">
<label><strong>従業員を選択:</strong></label>
<select id="employeeSelect" onchange="loadSalarySettings()">
<option value="">従業員を選択してください</option>
</select>
</div>
<div id="settingsContent" style="display: none">
<h2>給与設定履歴</h2>
<button class="btn btn-primary" onclick="showAddForm()">
新規設定追加
</button>
<div id="settingsList"></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="currentSetting"
style="
margin-top: 30px;
padding: 20px;
background: #e7f3ff;
border-radius: 5px;
"
>
<h3>現在の給与設定</h3>
<div id="currentSettingContent"></div>
</div>
</div>
</div>
<script>
const API_BASE = "http://localhost:8000";
let currentEmployeeId = null;
async function loadEmployees() {
try {
const response = await fetch(
`${API_BASE}/payroll/employees/?is_active=true`
);
const employees = await response.json();
const select = document.getElementById("employeeSelect");
select.innerHTML =
'<option value="">従業員を選択してください</option>' +
employees
.map(
(emp) =>
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`
)
.join("");
} 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";
try {
// 給与設定履歴を取得
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${employeeId}`
);
const settings = await response.json();
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);
} catch (error) {
alert("給与設定の取得に失敗しました");
console.error(error);
}
}
async function loadCurrentSetting(employeeId) {
try {
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${employeeId}/current`
);
if (response.ok) {
const setting = await response.json();
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>
`;
document.getElementById("currentSettingContent").innerHTML = html;
} else {
document.getElementById("currentSettingContent").innerHTML =
"<p>給与設定が登録されていません</p>";
}
} catch (error) {
document.getElementById("currentSettingContent").innerHTML =
"<p>給与設定の取得に失敗しました</p>";
}
}
function showAddForm() {
document.getElementById("addForm").style.display = "block";
// デフォルト値設定
document.querySelector('[name="valid_from"]').value = new Date()
.toISOString()
.split("T")[0];
}
function hideAddForm() {
document.getElementById("addForm").style.display = "none";
document.querySelector("#addForm form").reset();
}
async function saveSalarySetting(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
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;
if (data.hourly_rate) {
data.hourly_rate = parseFloat(data.hourly_rate);
} else {
delete data.hourly_rate;
}
if (!data.valid_to) {
delete data.valid_to;
}
try {
const response = await fetch(`${API_BASE}/payroll/settings/salary`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
alert("給与設定を保存しました");
hideAddForm();
loadSalarySettings();
} else {
const error = await response.json();
alert("保存に失敗しました: " + error.detail);
}
} catch (error) {
alert("保存に失敗しました");
console.error(error);
}
}
// 初期化
loadEmployees();
</script>
</body>
</html>