349 lines
12 KiB
HTML
349 lines
12 KiB
HTML
<!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 = "";
|
||
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>
|