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

@@ -56,311 +56,374 @@
table th {
background: #f0f0f0;
}
.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="employee-select">
<label><strong>従業員を選択:</strong></label>
<select id="employeeSelect" onchange="loadSalarySettings()">
<option value="">従業員を選択してください</option>
</select>
</div>
<button class="btn btn-primary" onclick="showAddForm()">
新規設定追加
</button>
<div id="settingsContent" style="display: none">
<h2>給与設定履歴</h2>
<button class="btn btn-primary" onclick="showAddForm()">
新規設定追加
</button>
<div id="settingsList"></div>
<div id="settingsList" style="margin-top: 20px"></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="addForm"
style="
display: none;
margin-top: 20px;
padding: 20px;
border: 2px solid #007bff;
border-radius: 5px;
"
>
<h3 id="formTitle">給与設定の追加</h3>
<form onsubmit="saveSalarySetting(event)">
<input type="hidden" name="setting_id" id="setting_id" />
<div
id="currentSetting"
style="
margin-top: 30px;
padding: 20px;
background: #e7f3ff;
border-radius: 5px;
"
>
<h3>現在の給与設定</h3>
<div id="currentSettingContent"></div>
</div>
<div class="form-group">
<label>従業員 *</label>
<select name="employee_id" id="employee_id" required>
<option value="">選択してください</option>
</select>
</div>
<div class="form-group">
<label>基本給 (円) *</label>
<input
type="number"
name="base_salary"
id="base_salary"
step="0.01"
required
/>
</div>
<div class="form-group">
<label>時給 (円)</label>
<input
type="number"
name="hourly_rate"
id="hourly_rate"
step="0.01"
/>
<small>時給制の場合に入力してください</small>
</div>
<div class="form-group">
<label>雇用形態 *</label>
<select name="employment_type" id="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" id="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"
id="commute_allowance"
step="0.01"
value="0"
/>
</div>
<div class="form-group">
<label>その他手当 (円)</label>
<input
type="number"
name="other_allowance"
id="other_allowance"
step="0.01"
value="0"
/>
</div>
<div class="form-group">
<label>適用開始日 *</label>
<input
type="date"
name="valid_from"
id="valid_from"
required
min="1900-01-01"
max="2099-12-31"
/>
</div>
<div class="form-group">
<label>適用終了日</label>
<input
type="date"
name="valid_to"
id="valid_to"
min="1900-01-01"
max="2099-12-31"
/>
<small>通常は空欄のままにしてください</small>
</div>
<button type="submit" class="btn btn-primary">保存</button>
<button
type="button"
class="btn btn-secondary"
onclick="hideAddForm()"
>
キャンセル
</button>
</form>
</div>
</div>
<script>
const API_BASE = "http://localhost:8000";
let currentEmployeeId = null;
const API_BASE = "http://localhost:18080";
let employees = [];
async function loadEmployees() {
try {
const response = await fetch(
`${API_BASE}/payroll/employees/?is_active=true`
);
const employees = await response.json();
employees = await response.json();
const select = document.getElementById("employeeSelect");
const select = document.getElementById("employee_id");
select.innerHTML =
'<option value="">従業員を選択してください</option>' +
'<option value="">選択してください</option>' +
employees
.map(
(emp) =>
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`
)
.join("");
loadAllSalarySettings();
} 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";
async function loadAllSalarySettings() {
try {
// 給与設定履歴を取得
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${employeeId}`
);
const settings = await response.json();
// すべての従業員の給与設定を取得
const allSettings = [];
for (const emp of employees) {
try {
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${emp.employee_id}/current`
);
if (response.ok) {
const setting = await response.json();
allSettings.push({
...setting,
employee_code: emp.employee_code,
employee_name: emp.name,
employee_id: emp.employee_id,
});
}
} catch (error) {
console.error(
`従業員 ${emp.employee_id} の給与設定取得失敗`,
error
);
}
}
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);
displayAllSettings(allSettings);
} catch (error) {
alert("給与設定の取得に失敗しました");
console.error(error);
}
}
async function loadCurrentSetting(employeeId) {
function displayAllSettings(settings) {
const html = `
<table>
<thead>
<tr>
<th>従業員コード</th>
<th>氏名</th>
<th>基本給</th>
<th>時給</th>
<th>雇用形態</th>
<th>支給形態</th>
<th>通勤手当</th>
<th>その他手当</th>
<th>適用期間</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${
settings.length > 0
? settings
.map(
(s) => `
<tr>
<td>${s.employee_code}</td>
<td>${s.employee_name}</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>
<td>${s.valid_from} ${s.valid_to || "現在"}</td>
<td>
<button class="btn btn-primary" onclick="editSetting(${
s.setting_id
})" style="padding: 5px 10px; font-size: 12px;">編集</button>
</td>
</tr>
`
)
.join("")
: '<tr><td colspan="10" style="text-align:center;">給与設定が登録されていません</td></tr>'
}
</tbody>
</table>
`;
document.getElementById("settingsList").innerHTML = html;
}
async function editSetting(settingId) {
try {
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${employeeId}/current`
);
if (response.ok) {
const setting = await response.json();
// 設定IDから詳細を取得する必要がありますが、現在のAPIでは難しいので
// 全従業員の設定を取得して該当するものを探す
for (const emp of employees) {
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${emp.employee_id}`
);
const settings = await response.json();
const setting = settings.find((s) => s.setting_id === settingId);
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>
`;
if (setting) {
// フォームに値を設定
document.getElementById("formTitle").textContent =
"給与設定の編集";
document.getElementById("setting_id").value = setting.setting_id;
document.getElementById("employee_id").value = emp.employee_id;
document.getElementById("employee_id").disabled = true; // 従業員は変更不可
document.getElementById("base_salary").value =
setting.base_salary;
document.getElementById("hourly_rate").value =
setting.hourly_rate || "";
document.getElementById("employment_type").value =
setting.employment_type;
document.getElementById("payment_type").value =
setting.payment_type;
document.getElementById("commute_allowance").value =
setting.commute_allowance;
document.getElementById("other_allowance").value =
setting.other_allowance;
document.getElementById("valid_from").value = setting.valid_from;
document.getElementById("valid_to").value =
setting.valid_to || "";
document.getElementById("currentSettingContent").innerHTML = html;
} else {
document.getElementById("currentSettingContent").innerHTML =
"<p>給与設定が登録されていません</p>";
document.getElementById("addForm").style.display = "block";
return;
}
}
} catch (error) {
document.getElementById("currentSettingContent").innerHTML =
"<p>給与設定の取得に失敗しました</p>";
alert("給与設定の取得に失敗しました");
console.error(error);
}
}
function showAddForm() {
document.getElementById("formTitle").textContent = "給与設定の追加";
document.getElementById("addForm").style.display = "block";
document.querySelector("#addForm form").reset();
document.getElementById("setting_id").value = "";
document.getElementById("employee_id").disabled = false;
// デフォルト値設定
document.querySelector('[name="valid_from"]').value = new Date()
document.getElementById("valid_from").value = new Date()
.toISOString()
.split("T")[0];
document.getElementById("commute_allowance").value = "0";
document.getElementById("other_allowance").value = "0";
}
function hideAddForm() {
document.getElementById("addForm").style.display = "none";
document.querySelector("#addForm form").reset();
document.getElementById("employee_id").disabled = false;
}
async function saveSalarySetting(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
const settingId = document.getElementById("setting_id").value;
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;
const data = {
employee_id: parseInt(formData.get("employee_id")),
base_salary: parseFloat(formData.get("base_salary")) || 0,
employment_type: formData.get("employment_type"),
payment_type: formData.get("payment_type"),
commute_allowance: parseFloat(formData.get("commute_allowance")) || 0,
other_allowance: parseFloat(formData.get("other_allowance")) || 0,
valid_from: formData.get("valid_from"),
};
if (data.hourly_rate) {
data.hourly_rate = parseFloat(data.hourly_rate);
} else {
delete data.hourly_rate;
if (formData.get("hourly_rate")) {
data.hourly_rate = parseFloat(formData.get("hourly_rate"));
}
if (!data.valid_to) {
delete data.valid_to;
if (formData.get("valid_to")) {
data.valid_to = formData.get("valid_to");
}
try {
const response = await fetch(`${API_BASE}/payroll/settings/salary`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
let response;
if (settingId) {
// 更新
response = await fetch(
`${API_BASE}/payroll/settings/salary/${settingId}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}
);
} else {
// 新規追加
response = await fetch(`${API_BASE}/payroll/settings/salary`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
}
if (response.ok) {
alert("給与設定を保存しました");
alert(
settingId ? "給与設定を更新しました" : "給与設定を保存しました"
);
hideAddForm();
loadSalarySettings();
loadAllSalarySettings();
} else {
const error = await response.json();
alert("保存に失敗しました: " + error.detail);