1554 lines
58 KiB
HTML
1554 lines
58 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>
|
||
.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;
|
||
}
|
||
.employee-list {
|
||
margin-top: 20px;
|
||
}
|
||
.employee-item {
|
||
padding: 15px;
|
||
border: 1px solid #ddd;
|
||
margin-bottom: 10px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
}
|
||
.employee-item:hover {
|
||
background: #f9f9f9;
|
||
}
|
||
.form-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: bold;
|
||
}
|
||
.form-group input,
|
||
.form-group select,
|
||
.form-group textarea {
|
||
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;
|
||
}
|
||
.btn-danger {
|
||
background: #dc3545;
|
||
color: white;
|
||
}
|
||
.dependent-list {
|
||
margin-top: 20px;
|
||
padding: 15px;
|
||
background: #f9f9f9;
|
||
border-radius: 5px;
|
||
}
|
||
.dependent-item {
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
margin-bottom: 10px;
|
||
background: white;
|
||
border-radius: 4px;
|
||
}
|
||
.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="nav-tabs">
|
||
<button class="nav-tab active" onclick="showTab('list')">
|
||
従業員一覧
|
||
</button>
|
||
<button class="nav-tab" onclick="showTab('create')">新規登録</button>
|
||
<button
|
||
class="nav-tab"
|
||
onclick="showTab('detail')"
|
||
id="detailTab"
|
||
style="display: none"
|
||
>
|
||
従業員詳細
|
||
</button>
|
||
<button
|
||
class="nav-tab"
|
||
onclick="showTab('edit')"
|
||
id="editTab"
|
||
style="display: none"
|
||
>
|
||
従業員編集
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 従業員一覧タブ -->
|
||
<div id="tab-list" class="tab-content active">
|
||
<div class="filters">
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
id="filterActive"
|
||
checked
|
||
onchange="loadEmployees()"
|
||
/>
|
||
在職中のみ表示
|
||
</label>
|
||
</div>
|
||
<div id="employeeList" class="employee-list"></div>
|
||
</div>
|
||
|
||
<!-- 新規登録タブ -->
|
||
<div id="tab-create" class="tab-content">
|
||
<h2>従業員の新規登録</h2>
|
||
<form id="createForm" onsubmit="createEmployee(event)">
|
||
<div class="form-group">
|
||
<label>従業員コード *</label>
|
||
<input
|
||
type="text"
|
||
name="employee_code"
|
||
required
|
||
placeholder="例: 20260001"
|
||
/>
|
||
<small style="color: #666"
|
||
>形式: 年+4桁の連番(例: 2026年入社の1番目は20260001)</small
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>氏名 *</label>
|
||
<input type="text" name="name" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>フリガナ</label>
|
||
<input type="text" name="name_kana" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>生年月日 *</label>
|
||
<input
|
||
type="date"
|
||
name="birth_date"
|
||
required
|
||
min="1900-01-01"
|
||
max="2026-12-31"
|
||
/>
|
||
<small style="color: #666"
|
||
>介護保険料の計算に使用されます(40歳以上が対象)</small
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>性別</label>
|
||
<select name="gender">
|
||
<option value="">選択してください</option>
|
||
<option value="男性">男性</option>
|
||
<option value="女性">女性</option>
|
||
<option value="その他">その他</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>マイナンバーカード番号</label>
|
||
<input
|
||
type="text"
|
||
name="my_number"
|
||
pattern="\d{12}"
|
||
maxlength="12"
|
||
placeholder="12桁の数字"
|
||
/>
|
||
<small style="color: #666"
|
||
>12桁の数字を入力してください(機密情報として管理されます)</small
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>メールアドレス</label>
|
||
<input type="email" name="email" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>電話番号</label>
|
||
<input type="tel" name="phone" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>入社日 *</label>
|
||
<input
|
||
type="date"
|
||
name="hire_date"
|
||
required
|
||
min="1900-01-01"
|
||
max="2099-12-31"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>郵便番号</label>
|
||
<input type="text" name="postal_code" placeholder="例: 123-4567" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>住所</label>
|
||
<input type="text" name="address" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>銀行名</label>
|
||
<input type="text" name="bank_name" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>支店名</label>
|
||
<input type="text" name="bank_branch" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>口座番号</label>
|
||
<input type="text" name="bank_account_number" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>口座種別</label>
|
||
<select name="bank_account_type">
|
||
<option value="">選択してください</option>
|
||
<option value="普通">普通</option>
|
||
<option value="当座">当座</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>備考</label>
|
||
<textarea name="notes" rows="3"></textarea>
|
||
</div>
|
||
|
||
<h3
|
||
style="
|
||
margin-top: 30px;
|
||
border-bottom: 2px solid #007bff;
|
||
padding-bottom: 10px;
|
||
"
|
||
>
|
||
📸 写真・身分証明書
|
||
</h3>
|
||
|
||
<div class="form-group">
|
||
<label>本人写真</label>
|
||
<input
|
||
type="file"
|
||
name="photo"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'photoPreview')"
|
||
/>
|
||
<div id="photoPreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>マイナンバーカード</label>
|
||
<input
|
||
type="file"
|
||
name="my_number_card_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'myNumberPreview')"
|
||
/>
|
||
<div id="myNumberPreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>在留カード</label>
|
||
<input
|
||
type="file"
|
||
name="residence_card_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'residencePreview')"
|
||
/>
|
||
<div id="residencePreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>健康保険証</label>
|
||
<input
|
||
type="file"
|
||
name="health_insurance_card_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'healthInsurancePreview')"
|
||
/>
|
||
<div id="healthInsurancePreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>その他の身分証明書</label>
|
||
<input
|
||
type="file"
|
||
name="other_id_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'otherIdPreview')"
|
||
/>
|
||
<div id="otherIdPreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary">登録</button>
|
||
<button
|
||
type="button"
|
||
class="btn btn-secondary"
|
||
onclick="showTab('list')"
|
||
>
|
||
キャンセル
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- 従業員詳細タブ -->
|
||
<div id="tab-detail" class="tab-content">
|
||
<div id="employeeDetail"></div>
|
||
</div>
|
||
|
||
<!-- 従業員編集タブ -->
|
||
<div id="tab-edit" class="tab-content">
|
||
<h2>従業員情報の編集</h2>
|
||
<form id="editForm" onsubmit="updateEmployee(event)">
|
||
<input type="hidden" name="employee_id" id="edit_employee_id" />
|
||
|
||
<div class="form-group">
|
||
<label>従業員コード *</label>
|
||
<input
|
||
type="text"
|
||
name="employee_code"
|
||
id="edit_employee_code"
|
||
required
|
||
placeholder="例: 20260001"
|
||
/>
|
||
<small style="color: #666"
|
||
>形式: 年+4桁の連番(例: 2026年入社の1番目は20260001)</small
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>氏名 *</label>
|
||
<input type="text" name="name" id="edit_name" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>フリガナ</label>
|
||
<input type="text" name="name_kana" id="edit_name_kana" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>生年月日 *</label>
|
||
<input
|
||
type="date"
|
||
name="birth_date"
|
||
id="edit_birth_date"
|
||
required
|
||
min="1900-01-01"
|
||
max="2026-12-31"
|
||
/>
|
||
<small style="color: #666"
|
||
>介護保険料の計算に使用されます(40歳以上が対象)</small
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>性別</label>
|
||
<select name="gender" id="edit_gender">
|
||
<option value="">選択してください</option>
|
||
<option value="男性">男性</option>
|
||
<option value="女性">女性</option>
|
||
<option value="その他">その他</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>マイナンバーカード番号</label>
|
||
<input
|
||
type="text"
|
||
name="my_number"
|
||
id="edit_my_number"
|
||
pattern="\d{12}"
|
||
maxlength="12"
|
||
placeholder="12桁の数字"
|
||
/>
|
||
<small style="color: #666"
|
||
>12桁の数字を入力してください(機密情報として管理されます)</small
|
||
>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>メールアドレス</label>
|
||
<input type="email" name="email" id="edit_email" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>電話番号</label>
|
||
<input type="tel" name="phone" id="edit_phone" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>入社日 *</label>
|
||
<input
|
||
type="date"
|
||
name="hire_date"
|
||
id="edit_hire_date"
|
||
required
|
||
min="1900-01-01"
|
||
max="2099-12-31"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>郵便番号</label>
|
||
<input
|
||
type="text"
|
||
name="postal_code"
|
||
id="edit_postal_code"
|
||
placeholder="例: 123-4567"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>住所</label>
|
||
<input type="text" name="address" id="edit_address" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>銀行名</label>
|
||
<input type="text" name="bank_name" id="edit_bank_name" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>支店名</label>
|
||
<input type="text" name="bank_branch" id="edit_bank_branch" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>口座番号</label>
|
||
<input
|
||
type="text"
|
||
name="bank_account_number"
|
||
id="edit_bank_account_number"
|
||
/>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>口座種別</label>
|
||
<select name="bank_account_type" id="edit_bank_account_type">
|
||
<option value="">選択してください</option>
|
||
<option value="普通">普通</option>
|
||
<option value="当座">当座</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>備考</label>
|
||
<textarea name="notes" id="edit_notes" rows="3"></textarea>
|
||
</div>
|
||
|
||
<h3
|
||
style="
|
||
margin-top: 30px;
|
||
border-bottom: 2px solid #007bff;
|
||
padding-bottom: 10px;
|
||
"
|
||
>
|
||
📸 写真・身分証明書
|
||
</h3>
|
||
|
||
<div class="form-group">
|
||
<label>本人写真</label>
|
||
<div id="edit_photo_current"></div>
|
||
<input
|
||
type="file"
|
||
name="photo"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'edit_photoPreview')"
|
||
/>
|
||
<div id="edit_photoPreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>マイナンバーカード</label>
|
||
<div id="edit_my_number_card_current"></div>
|
||
<input
|
||
type="file"
|
||
name="my_number_card_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'edit_myNumberPreview')"
|
||
/>
|
||
<div id="edit_myNumberPreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>在留カード</label>
|
||
<div id="edit_residence_card_current"></div>
|
||
<input
|
||
type="file"
|
||
name="residence_card_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'edit_residencePreview')"
|
||
/>
|
||
<div id="edit_residencePreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>健康保険証</label>
|
||
<div id="edit_health_insurance_current"></div>
|
||
<input
|
||
type="file"
|
||
name="health_insurance_card_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'edit_healthInsurancePreview')"
|
||
/>
|
||
<div
|
||
id="edit_healthInsurancePreview"
|
||
style="margin-top: 10px"
|
||
></div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>その他の身分証明書</label>
|
||
<div id="edit_other_id_current"></div>
|
||
<input
|
||
type="file"
|
||
name="other_id_image"
|
||
accept="image/*"
|
||
onchange="previewImage(event, 'edit_otherIdPreview')"
|
||
/>
|
||
<div id="edit_otherIdPreview" style="margin-top: 10px"></div>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary">更新</button>
|
||
<button
|
||
type="button"
|
||
class="btn btn-secondary"
|
||
onclick="closeDetailTab()"
|
||
>
|
||
キャンセル
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const API_BASE = "http://localhost:18080";
|
||
let currentEmployee = null;
|
||
|
||
// 年齢計算関数
|
||
function calculateAge(birthDate) {
|
||
const today = new Date();
|
||
const birth = new Date(birthDate);
|
||
let age = today.getFullYear() - birth.getFullYear();
|
||
const monthDiff = today.getMonth() - birth.getMonth();
|
||
if (
|
||
monthDiff < 0 ||
|
||
(monthDiff === 0 && today.getDate() < birth.getDate())
|
||
) {
|
||
age--;
|
||
}
|
||
return age;
|
||
}
|
||
|
||
// 画像プレビュー関数
|
||
function previewImage(event, previewId) {
|
||
const file = event.target.files[0];
|
||
const preview = document.getElementById(previewId);
|
||
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onload = function (e) {
|
||
preview.innerHTML = `<img src="${e.target.result}" style="max-width: 300px; max-height: 200px; border: 1px solid #ddd; border-radius: 4px;" />`;
|
||
};
|
||
reader.readAsDataURL(file);
|
||
} else {
|
||
preview.innerHTML = "";
|
||
}
|
||
}
|
||
|
||
// ファイルをBase64に変換(旧方式 - 使用しない)
|
||
// function fileToBase64(file) {
|
||
// return new Promise((resolve, reject) => {
|
||
// const reader = new FileReader();
|
||
// reader.onload = () => resolve(reader.result);
|
||
// reader.onerror = reject;
|
||
// reader.readAsDataURL(file);
|
||
// });
|
||
// }
|
||
|
||
// ファイルをサーバーにアップロード(新方式)
|
||
async function uploadFileToServer(
|
||
file,
|
||
employeeCode,
|
||
fileType = "image"
|
||
) {
|
||
const formData = new FormData();
|
||
formData.append("file", file);
|
||
formData.append("file_type", fileType);
|
||
|
||
try {
|
||
const response = await fetch(
|
||
`${API_BASE}/files/upload/employee/${employeeCode}`,
|
||
{
|
||
method: "POST",
|
||
body: formData,
|
||
}
|
||
);
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.detail || "アップロードに失敗しました");
|
||
}
|
||
|
||
const result = await response.json();
|
||
return result.file_path; // サーバーから返されたファイルパス
|
||
} catch (error) {
|
||
console.error("ファイルアップロードエラー:", error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
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 === "list") {
|
||
document.querySelector(".nav-tab").classList.add("active");
|
||
document.getElementById("tab-list").classList.add("active");
|
||
loadEmployees();
|
||
} else if (tabName === "create") {
|
||
document.querySelectorAll(".nav-tab")[1].classList.add("active");
|
||
document.getElementById("tab-create").classList.add("active");
|
||
} else if (tabName === "detail") {
|
||
document.getElementById("detailTab").classList.add("active");
|
||
document.getElementById("tab-detail").classList.add("active");
|
||
} else if (tabName === "edit") {
|
||
document.getElementById("editTab").classList.add("active");
|
||
document.getElementById("tab-edit").classList.add("active");
|
||
}
|
||
}
|
||
|
||
// タブを閉じる関数
|
||
function closeDetailTab() {
|
||
document.getElementById("detailTab").style.display = "none";
|
||
document.getElementById("editTab").style.display = "none";
|
||
showTab("list");
|
||
}
|
||
|
||
async function loadEmployees() {
|
||
const isActive = document.getElementById("filterActive").checked;
|
||
const url = `${API_BASE}/payroll/employees/${
|
||
isActive ? "?is_active=true" : ""
|
||
}`;
|
||
|
||
try {
|
||
const response = await fetch(url);
|
||
const employees = await response.json();
|
||
|
||
const html = employees
|
||
.map(
|
||
(emp) => `
|
||
<div class="employee-item" onclick="viewEmployee(${
|
||
emp.employee_id
|
||
})">
|
||
<strong>${emp.employee_code}</strong> - ${emp.name}
|
||
${emp.name_kana ? `(${emp.name_kana})` : ""}
|
||
<br>
|
||
<small>入社日: ${emp.hire_date} ${
|
||
emp.termination_date ? ` | 退職日: ${emp.termination_date}` : ""
|
||
}</small>
|
||
</div>
|
||
`
|
||
)
|
||
.join("");
|
||
|
||
document.getElementById("employeeList").innerHTML =
|
||
html || "<p>従業員が登録されていません</p>";
|
||
} catch (error) {
|
||
alert("従業員一覧の取得に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
async function createEmployee(event) {
|
||
event.preventDefault();
|
||
const form = event.target;
|
||
|
||
try {
|
||
// まず従業員コードを取得
|
||
const employeeCode = form.employee_code.value;
|
||
|
||
// 写真ファイルをアップロード
|
||
const photoUploads = {};
|
||
const photoFields = [
|
||
{ name: "photo", field: "photo_url" },
|
||
{ name: "my_number_card_image", field: "my_number_card_image_url" },
|
||
{ name: "residence_card_image", field: "residence_card_image_url" },
|
||
{
|
||
name: "health_insurance_card_image",
|
||
field: "health_insurance_card_image_url",
|
||
},
|
||
{ name: "other_id_image", field: "other_id_image_url" },
|
||
];
|
||
|
||
for (const { name, field } of photoFields) {
|
||
const fileInput = form[name];
|
||
if (fileInput.files[0]) {
|
||
try {
|
||
const filePath = await uploadFileToServer(
|
||
fileInput.files[0],
|
||
employeeCode,
|
||
"image"
|
||
);
|
||
photoUploads[field] = filePath;
|
||
} catch (error) {
|
||
alert(`${name}のアップロードに失敗しました: ${error.message}`);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 従業員データを作成
|
||
const data = {
|
||
employee_code: employeeCode,
|
||
name: form.name.value,
|
||
name_kana: form.name_kana.value || null,
|
||
birth_date: form.birth_date.value,
|
||
gender: form.gender.value || null,
|
||
my_number: form.my_number.value || null,
|
||
email: form.email.value || null,
|
||
phone: form.phone.value || null,
|
||
hire_date: form.hire_date.value,
|
||
postal_code: form.postal_code.value || null,
|
||
address: form.address.value || null,
|
||
bank_name: form.bank_name.value || null,
|
||
bank_branch: form.bank_branch.value || null,
|
||
bank_account_number: form.bank_account_number.value || null,
|
||
bank_account_type: form.bank_account_type.value || null,
|
||
notes: form.notes.value || null,
|
||
...photoUploads, // アップロードされたファイルパスを追加
|
||
};
|
||
|
||
const response = await fetch(`${API_BASE}/payroll/employees/`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(data),
|
||
});
|
||
|
||
if (response.ok) {
|
||
alert("従業員を登録しました");
|
||
form.reset();
|
||
// プレビューをクリア
|
||
[
|
||
"photoPreview",
|
||
"myNumberPreview",
|
||
"residencePreview",
|
||
"healthInsurancePreview",
|
||
"otherIdPreview",
|
||
].forEach((id) => {
|
||
document.getElementById(id).innerHTML = "";
|
||
});
|
||
showTab("list");
|
||
} else {
|
||
const error = await response.json();
|
||
alert("登録に失敗しました: " + error.detail);
|
||
}
|
||
} catch (error) {
|
||
alert("登録に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
async function viewEmployee(employeeId) {
|
||
try {
|
||
const response = await fetch(
|
||
`${API_BASE}/payroll/employees/${employeeId}`
|
||
);
|
||
const employee = await response.json();
|
||
currentEmployee = employee;
|
||
|
||
const html = `
|
||
<h2>${employee.name} (${employee.employee_code})</h2>
|
||
<p><strong>フリガナ:</strong> ${
|
||
employee.name_kana || "-"
|
||
}</p>
|
||
<p><strong>生年月日:</strong> ${
|
||
employee.birth_date || "-"
|
||
} ${
|
||
employee.birth_date
|
||
? `(${calculateAge(employee.birth_date)}歳)`
|
||
: ""
|
||
}</p>
|
||
<p><strong>性別:</strong> ${employee.gender || "-"}</p>
|
||
<p><strong>マイナンバー:</strong> ${
|
||
employee.my_number
|
||
? `${employee.my_number.substring(
|
||
0,
|
||
4
|
||
)}-${employee.my_number.substring(
|
||
4,
|
||
8
|
||
)}-${employee.my_number.substring(8, 12)}`
|
||
: "-"
|
||
}</p>
|
||
<p><strong>メール:</strong> ${employee.email || "-"}</p>
|
||
<p><strong>電話:</strong> ${employee.phone || "-"}</p>
|
||
<p><strong>郵便番号:</strong> ${
|
||
employee.postal_code || "-"
|
||
}</p>
|
||
<p><strong>住所:</strong> ${employee.address || "-"}</p>
|
||
<p><strong>入社日:</strong> ${employee.hire_date}</p>
|
||
<p><strong>退職日:</strong> ${
|
||
employee.termination_date || "-"
|
||
}</p>
|
||
<p><strong>銀行:</strong> ${employee.bank_name || "-"} ${
|
||
employee.bank_branch || ""
|
||
}</p>
|
||
<p><strong>口座:</strong> ${
|
||
employee.bank_account_type || ""
|
||
} ${employee.bank_account_number || "-"}</p>
|
||
<p><strong>備考:</strong> ${employee.notes || "-"}</p>
|
||
|
||
<h3 style="margin-top: 30px; border-bottom: 2px solid #007bff; padding-bottom: 10px;">📸 写真・身分証明書</h3>
|
||
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; margin-top: 20px;">
|
||
${
|
||
employee.photo_url
|
||
? `
|
||
<div>
|
||
<strong>本人写真</strong><br>
|
||
<img src="${
|
||
employee.photo_url.startsWith("data:")
|
||
? employee.photo_url
|
||
: API_BASE + employee.photo_url
|
||
}" style="max-width: 100%; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px;" />
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
${
|
||
employee.my_number_card_image_url
|
||
? `
|
||
<div>
|
||
<strong>マイナンバーカード</strong><br>
|
||
<img src="${
|
||
employee.my_number_card_image_url.startsWith(
|
||
"data:"
|
||
)
|
||
? employee.my_number_card_image_url
|
||
: API_BASE + employee.my_number_card_image_url
|
||
}" style="max-width: 100%; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px;" />
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
${
|
||
employee.residence_card_image_url
|
||
? `
|
||
<div>
|
||
<strong>在留カード</strong><br>
|
||
<img src="${
|
||
employee.residence_card_image_url.startsWith(
|
||
"data:"
|
||
)
|
||
? employee.residence_card_image_url
|
||
: API_BASE + employee.residence_card_image_url
|
||
}" style="max-width: 100%; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px;" />
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
${
|
||
employee.health_insurance_card_image_url
|
||
? `
|
||
<div>
|
||
<strong>健康保険証</strong><br>
|
||
<img src="${
|
||
employee.health_insurance_card_image_url.startsWith(
|
||
"data:"
|
||
)
|
||
? employee.health_insurance_card_image_url
|
||
: API_BASE +
|
||
employee.health_insurance_card_image_url
|
||
}" style="max-width: 100%; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px;" />
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
${
|
||
employee.other_id_image_url
|
||
? `
|
||
<div>
|
||
<strong>その他の身分証明書</strong><br>
|
||
<img src="${
|
||
employee.other_id_image_url.startsWith("data:")
|
||
? employee.other_id_image_url
|
||
: API_BASE + employee.other_id_image_url
|
||
}" style="max-width: 100%; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px;" />
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
</div>
|
||
|
||
<div class="dependent-list">
|
||
<h3>扶養家族 <button class="btn btn-primary" onclick="showAddDependentForm(${
|
||
employee.employee_id
|
||
})" style="padding: 5px 10px; font-size: 12px;">扶養家族追加</button></h3>
|
||
${(() => {
|
||
// 源泉税控除対象人数を計算(19歳以上または70歳以上)
|
||
let koureiCount = 0;
|
||
if (
|
||
employee.dependents &&
|
||
employee.dependents.length > 0
|
||
) {
|
||
koureiCount = employee.dependents.filter((dep) => {
|
||
const taxStatus = getTaxDeductionStatus(
|
||
dep.birth_date
|
||
);
|
||
// 源泉税控除対象は19歳以上(一般扶養親族16-18歳は住民税のみなので除外)
|
||
return (
|
||
taxStatus?.isDeductible &&
|
||
(taxStatus.type === "控除対象扶養親族" ||
|
||
taxStatus.type === "老人扶養親族")
|
||
);
|
||
}).length;
|
||
}
|
||
|
||
return `
|
||
<div style="background: #f0f8ff; padding: 10px; border-radius: 5px; margin-bottom: 15px;">
|
||
<strong style="color: #0066cc;">甲欄扶養親族人数(源泉税控除対象): ${koureiCount}人</strong>
|
||
<small style="display: block; margin-top: 5px; color: #666;">
|
||
※19歳以上の扶養親族が対象(16-18歳は住民税のみ対象のため含まれません)
|
||
</small>
|
||
</div>
|
||
`;
|
||
})()}
|
||
${
|
||
employee.dependents && employee.dependents.length > 0
|
||
? employee.dependents
|
||
.map((dep) => {
|
||
const taxStatus = getTaxDeductionStatus(
|
||
dep.birth_date
|
||
);
|
||
return `
|
||
<div class="dependent-item">
|
||
<strong>${dep.name}</strong> (${
|
||
dep.relationship
|
||
})
|
||
${
|
||
dep.birth_date
|
||
? ` - 生年月日: ${dep.birth_date} (${taxStatus?.age}歳)`
|
||
: ""
|
||
}
|
||
${
|
||
taxStatus?.isDeductible
|
||
? ` <span style="color:${taxStatus.color}; font-weight:bold;">[${taxStatus.label}]</span>`
|
||
: ""
|
||
}
|
||
${
|
||
dep.is_spouse
|
||
? ' <span style="color:blue;">[配偶者]</span>'
|
||
: ""
|
||
}
|
||
${
|
||
dep.is_disabled
|
||
? ' <span style="color:red;">[障害者]</span>'
|
||
: ""
|
||
}
|
||
<br><small>有効期間: ${dep.valid_from} ~ ${
|
||
dep.valid_to || "現在"
|
||
}</small>
|
||
<button class="btn btn-primary" onclick='editDependent(${JSON.stringify(
|
||
dep
|
||
)})' style="padding: 3px 8px; font-size: 11px; margin-left: 10px;">編集</button>
|
||
<button class="btn btn-secondary" onclick="deleteDependent(${
|
||
dep.dependent_id
|
||
})" style="padding: 3px 8px; font-size: 11px; margin-left: 5px;">削除</button>
|
||
</div>
|
||
`;
|
||
})
|
||
.join("")
|
||
: "<p>扶養家族が登録されていません</p>"
|
||
}
|
||
${(() => {
|
||
// 源泉税控除対象人数を再計算(説明用)
|
||
let koureiCount = 0;
|
||
if (
|
||
employee.dependents &&
|
||
employee.dependents.length > 0
|
||
) {
|
||
koureiCount = employee.dependents.filter((dep) => {
|
||
const taxStatus = getTaxDeductionStatus(
|
||
dep.birth_date
|
||
);
|
||
return (
|
||
taxStatus?.isDeductible &&
|
||
(taxStatus.type === "控除対象扶養親族" ||
|
||
taxStatus.type === "老人扶養親族")
|
||
);
|
||
}).length;
|
||
}
|
||
|
||
return `
|
||
<div style="margin-top: 15px; padding: 10px; background: #fff9e6; border-left: 4px solid #ffc107; border-radius: 4px;">
|
||
<strong>📋 源泉税控除の説明:</strong>
|
||
<ul style="margin: 10px 0 0 20px; font-size: 13px; line-height: 1.6;">
|
||
<li><strong style="color: purple;">老人扶養親族(70歳以上)</strong>: 源泉税・住民税の両方で控除対象</li>
|
||
<li><strong style="color: green;">控除対象扶養親族(19歳以上)</strong>: 源泉税・住民税の両方で控除対象</li>
|
||
<li><strong style="color: orange;">一般扶養親族(16-18歳)</strong>: 住民税のみ控除対象(源泉税では控除されません)</li>
|
||
<li><strong style="color: #999;">16歳未満</strong>: 控除対象外</li>
|
||
</ul>
|
||
<p style="margin: 10px 0 0 0; font-size: 12px; color: #666;">
|
||
※給与計算時は<strong>甲欄扶養親族人数(${koureiCount}人)</strong>を使用して源泉税額を決定します。
|
||
</p>
|
||
</div>
|
||
`;
|
||
})()}
|
||
</div>
|
||
|
||
<button class="btn btn-primary" onclick="editEmployee(${
|
||
employee.employee_id
|
||
})">編集</button>
|
||
<button class="btn btn-secondary" onclick="closeDetailTab()">閉じる</button>
|
||
`;
|
||
|
||
document.getElementById("employeeDetail").innerHTML = html;
|
||
document.getElementById("detailTab").style.display = "block";
|
||
showTab("detail");
|
||
} catch (error) {
|
||
alert("従業員情報の取得に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// 従業員編集
|
||
async function editEmployee(employeeId) {
|
||
try {
|
||
const response = await fetch(
|
||
`${API_BASE}/payroll/employees/${employeeId}`
|
||
);
|
||
const employee = await response.json();
|
||
currentEmployee = employee;
|
||
|
||
// 基本情報を設定
|
||
document.getElementById("edit_employee_id").value =
|
||
employee.employee_id;
|
||
document.getElementById("edit_employee_code").value =
|
||
employee.employee_code;
|
||
document.getElementById("edit_name").value = employee.name || "";
|
||
document.getElementById("edit_name_kana").value =
|
||
employee.name_kana || "";
|
||
document.getElementById("edit_birth_date").value =
|
||
employee.birth_date || "";
|
||
document.getElementById("edit_gender").value = employee.gender || "";
|
||
document.getElementById("edit_my_number").value =
|
||
employee.my_number || "";
|
||
document.getElementById("edit_email").value = employee.email || "";
|
||
document.getElementById("edit_phone").value = employee.phone || "";
|
||
document.getElementById("edit_hire_date").value =
|
||
employee.hire_date || "";
|
||
document.getElementById("edit_postal_code").value =
|
||
employee.postal_code || "";
|
||
document.getElementById("edit_address").value =
|
||
employee.address || "";
|
||
document.getElementById("edit_bank_name").value =
|
||
employee.bank_name || "";
|
||
document.getElementById("edit_bank_branch").value =
|
||
employee.bank_branch || "";
|
||
document.getElementById("edit_bank_account_number").value =
|
||
employee.bank_account_number || "";
|
||
document.getElementById("edit_bank_account_type").value =
|
||
employee.bank_account_type || "";
|
||
document.getElementById("edit_notes").value = employee.notes || "";
|
||
|
||
// 現在の写真を表示
|
||
const photoFields = [
|
||
{
|
||
field: "photo_url",
|
||
div: "edit_photo_current",
|
||
label: "現在の写真",
|
||
},
|
||
{
|
||
field: "my_number_card_image_url",
|
||
div: "edit_my_number_card_current",
|
||
label: "現在のマイナンバーカード",
|
||
},
|
||
{
|
||
field: "residence_card_image_url",
|
||
div: "edit_residence_card_current",
|
||
label: "現在の在留カード",
|
||
},
|
||
{
|
||
field: "health_insurance_card_image_url",
|
||
div: "edit_health_insurance_current",
|
||
label: "現在の健康保険証",
|
||
},
|
||
{
|
||
field: "other_id_image_url",
|
||
div: "edit_other_id_current",
|
||
label: "現在のその他ID",
|
||
},
|
||
];
|
||
|
||
photoFields.forEach(({ field, div, label }) => {
|
||
const divElement = document.getElementById(div);
|
||
if (employee[field]) {
|
||
// ファイルパスの場合は API_BASE を追加、Base64の場合はそのまま
|
||
const imageUrl = employee[field].startsWith("data:")
|
||
? employee[field]
|
||
: `${API_BASE}${employee[field]}`;
|
||
|
||
divElement.innerHTML = `
|
||
<strong>${label}:</strong><br>
|
||
<img src="${imageUrl}" style="max-width: 300px; max-height: 200px; border: 1px solid #ddd; border-radius: 4px; margin-top: 5px;" />
|
||
`;
|
||
} else {
|
||
divElement.innerHTML = `<small style="color: #999;">登録されていません</small>`;
|
||
}
|
||
});
|
||
|
||
// プレビューをクリア
|
||
[
|
||
"edit_photoPreview",
|
||
"edit_myNumberPreview",
|
||
"edit_residencePreview",
|
||
"edit_healthInsurancePreview",
|
||
"edit_otherIdPreview",
|
||
].forEach((id) => {
|
||
document.getElementById(id).innerHTML = "";
|
||
});
|
||
|
||
// 編集タブを表示
|
||
document.getElementById("editTab").style.display = "block";
|
||
showTab("edit");
|
||
} catch (error) {
|
||
alert("従業員情報の取得に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// 従業員更新
|
||
async function updateEmployee(event) {
|
||
event.preventDefault();
|
||
const form = event.target;
|
||
const employeeId = form.employee_id.value;
|
||
const employeeCode = form.employee_code.value;
|
||
|
||
try {
|
||
// 写真ファイルをアップロード
|
||
const photoUploads = {};
|
||
const photoFields = [
|
||
{ name: "photo", field: "photo_url" },
|
||
{ name: "my_number_card_image", field: "my_number_card_image_url" },
|
||
{ name: "residence_card_image", field: "residence_card_image_url" },
|
||
{
|
||
name: "health_insurance_card_image",
|
||
field: "health_insurance_card_image_url",
|
||
},
|
||
{ name: "other_id_image", field: "other_id_image_url" },
|
||
];
|
||
|
||
for (const { name, field } of photoFields) {
|
||
const fileInput = form[name];
|
||
if (fileInput.files[0]) {
|
||
try {
|
||
const filePath = await uploadFileToServer(
|
||
fileInput.files[0],
|
||
employeeCode,
|
||
"image"
|
||
);
|
||
photoUploads[field] = filePath;
|
||
} catch (error) {
|
||
alert(`${name}のアップロードに失敗しました: ${error.message}`);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
const data = {
|
||
employee_code: employeeCode,
|
||
name: form.name.value,
|
||
name_kana: form.name_kana.value || null,
|
||
birth_date: form.birth_date.value,
|
||
gender: form.gender.value || null,
|
||
my_number: form.my_number.value || null,
|
||
email: form.email.value || null,
|
||
phone: form.phone.value || null,
|
||
hire_date: form.hire_date.value,
|
||
postal_code: form.postal_code.value || null,
|
||
address: form.address.value || null,
|
||
bank_name: form.bank_name.value || null,
|
||
bank_branch: form.bank_branch.value || null,
|
||
bank_account_number: form.bank_account_number.value || null,
|
||
bank_account_type: form.bank_account_type.value || null,
|
||
notes: form.notes.value || null,
|
||
...photoUploads, // 新しくアップロードされたファイルパスのみ追加
|
||
};
|
||
|
||
const response = await fetch(
|
||
`${API_BASE}/payroll/employees/${employeeId}`,
|
||
{
|
||
method: "PUT",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(data),
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
alert("従業員情報を更新しました");
|
||
viewEmployee(employeeId);
|
||
} else {
|
||
const error = await response.json();
|
||
alert(`更新に失敗しました: ${error.detail}`);
|
||
}
|
||
} catch (error) {
|
||
alert("更新中にエラーが発生しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// 扶養家族追加フォームを表示
|
||
function showAddDependentForm(employeeId) {
|
||
const html = `
|
||
<div id="dependentFormModal" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 600px; 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 onsubmit="addDependent(event, ${employeeId})">
|
||
<div class="form-group">
|
||
<label>氏名 *</label>
|
||
<input type="text" name="name" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>続柄 *</label>
|
||
<select name="relationship" required>
|
||
<option value="">選択してください</option>
|
||
<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="date" name="birth_date" min="1900-01-01" max="2099-12-31" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" name="is_spouse" value="true" />
|
||
配偶者控除対象
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" name="is_disabled" value="true" />
|
||
障害者控除対象
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>年間所得金額 (円)</label>
|
||
<input type="number" name="income_amount" step="0.01" value="0" />
|
||
<small>配偶者・扶養控除の判定に使用</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>適用開始日 *</label>
|
||
<input type="date" name="valid_from" required min="1900-01-01" max="2099-12-31" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>適用終了日</label>
|
||
<input type="date" name="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="document.getElementById('dependentFormModal').remove()">キャンセル</button>
|
||
</form>
|
||
</div>
|
||
`;
|
||
|
||
const modal = document.createElement("div");
|
||
modal.innerHTML = html;
|
||
document.body.appendChild(modal);
|
||
|
||
// デフォルト値設定
|
||
const validFromInput = modal.querySelector('[name="valid_from"]');
|
||
validFromInput.value = new Date().toISOString().split("T")[0];
|
||
}
|
||
|
||
// 扶養家族を追加
|
||
async function addDependent(event, employeeId) {
|
||
event.preventDefault();
|
||
const form = event.target;
|
||
const formData = new FormData(form);
|
||
|
||
const data = {
|
||
name: formData.get("name"),
|
||
relationship: formData.get("relationship"),
|
||
birth_date: formData.get("birth_date") || null,
|
||
is_spouse: formData.get("is_spouse") === "true",
|
||
is_disabled: formData.get("is_disabled") === "true",
|
||
income_amount: parseFloat(formData.get("income_amount")) || 0,
|
||
valid_from: formData.get("valid_from"),
|
||
valid_to: formData.get("valid_to") || null,
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(
|
||
`${API_BASE}/payroll/employees/${employeeId}/dependents`,
|
||
{
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(data),
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
alert("扶養家族を追加しました");
|
||
document.getElementById("dependentFormModal").remove();
|
||
viewEmployee(employeeId);
|
||
} else {
|
||
const error = await response.json();
|
||
alert("追加に失敗しました: " + error.detail);
|
||
}
|
||
} catch (error) {
|
||
alert("追加に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// その年の12月31日時点の年齢を計算
|
||
function calculateAgeAtYearEnd(
|
||
birthDate,
|
||
targetYear = new Date().getFullYear()
|
||
) {
|
||
if (!birthDate) return null;
|
||
const birth = new Date(birthDate);
|
||
const yearEnd = new Date(targetYear, 11, 31); // 12月31日
|
||
let age = yearEnd.getFullYear() - birth.getFullYear();
|
||
const monthDiff = yearEnd.getMonth() - birth.getMonth();
|
||
if (
|
||
monthDiff < 0 ||
|
||
(monthDiff === 0 && yearEnd.getDate() < birth.getDate())
|
||
) {
|
||
age--;
|
||
}
|
||
return age;
|
||
}
|
||
|
||
// 所得税・住民税控除対象の判定
|
||
function getTaxDeductionStatus(birthDate) {
|
||
const age = calculateAgeAtYearEnd(birthDate);
|
||
if (age === null) return null;
|
||
|
||
if (age >= 70) {
|
||
return {
|
||
isDeductible: true,
|
||
type: "老人扶養親族",
|
||
label: "老人扶養親族(源泉税・住民税)",
|
||
color: "purple",
|
||
age,
|
||
};
|
||
} else if (age >= 19) {
|
||
return {
|
||
isDeductible: true,
|
||
type: "控除対象扶養親族",
|
||
label: "控除対象(源泉税・住民税)",
|
||
color: "green",
|
||
age,
|
||
};
|
||
} else if (age >= 16) {
|
||
return {
|
||
isDeductible: true,
|
||
type: "一般扶養親族",
|
||
label: "一般扶養親族(住民税のみ)",
|
||
color: "orange",
|
||
age,
|
||
};
|
||
} else {
|
||
return {
|
||
isDeductible: false,
|
||
type: null,
|
||
label: null,
|
||
color: null,
|
||
age,
|
||
};
|
||
}
|
||
}
|
||
|
||
// 扶養家族を削除
|
||
async function deleteDependent(dependentId) {
|
||
if (!confirm("この扶養家族を削除しますか?")) return;
|
||
|
||
try {
|
||
const response = await fetch(
|
||
`${API_BASE}/payroll/employees/dependents/${dependentId}`,
|
||
{
|
||
method: "DELETE",
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
alert("扶養家族を削除しました");
|
||
viewEmployee(currentEmployee.employee_id);
|
||
} else {
|
||
const error = await response.json();
|
||
alert("削除に失敗しました: " + error.detail);
|
||
}
|
||
} catch (error) {
|
||
alert("削除に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// 扶養家族を編集
|
||
function editDependent(dependent) {
|
||
const html = `
|
||
<div id="dependentFormModal" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 600px; 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 onsubmit="updateDependent(event, ${dependent.dependent_id})">
|
||
<div class="form-group">
|
||
<label>氏名 *</label>
|
||
<input type="text" name="name" value="${
|
||
dependent.name
|
||
}" required />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>続柄 *</label>
|
||
<select name="relationship" required>
|
||
<option value="">選択してください</option>
|
||
<option value="配偶者" ${
|
||
dependent.relationship === "配偶者" ? "selected" : ""
|
||
}>配偶者</option>
|
||
<option value="子" ${
|
||
dependent.relationship === "子" ? "selected" : ""
|
||
}>子</option>
|
||
<option value="父" ${
|
||
dependent.relationship === "父" ? "selected" : ""
|
||
}>父</option>
|
||
<option value="母" ${
|
||
dependent.relationship === "母" ? "selected" : ""
|
||
}>母</option>
|
||
<option value="その他" ${
|
||
dependent.relationship === "その他" ? "selected" : ""
|
||
}>その他</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>生年月日</label>
|
||
<input type="date" name="birth_date" value="${
|
||
dependent.birth_date || ""
|
||
}" min="1900-01-01" max="2099-12-31" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" name="is_spouse" value="true" ${
|
||
dependent.is_spouse ? "checked" : ""
|
||
} />
|
||
配偶者控除対象
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>
|
||
<input type="checkbox" name="is_disabled" value="true" ${
|
||
dependent.is_disabled ? "checked" : ""
|
||
} />
|
||
障害者控除対象
|
||
</label>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>年間所得金額 (円)</label>
|
||
<input type="number" name="income_amount" step="0.01" value="${
|
||
dependent.income_amount || 0
|
||
}" />
|
||
<small>配偶者・扶養控除の判定に使用</small>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>適用開始日 *</label>
|
||
<input type="date" name="valid_from" value="${
|
||
dependent.valid_from
|
||
}" required min="1900-01-01" max="2099-12-31" />
|
||
</div>
|
||
<div class="form-group">
|
||
<label>適用終了日</label>
|
||
<input type="date" name="valid_to" value="${
|
||
dependent.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="document.getElementById('dependentFormModal').remove()">キャンセル</button>
|
||
</form>
|
||
</div>
|
||
`;
|
||
|
||
const modal = document.createElement("div");
|
||
modal.innerHTML = html;
|
||
document.body.appendChild(modal);
|
||
}
|
||
|
||
// 扶養家族を更新
|
||
async function updateDependent(event, dependentId) {
|
||
event.preventDefault();
|
||
const form = event.target;
|
||
const formData = new FormData(form);
|
||
|
||
const data = {
|
||
name: formData.get("name"),
|
||
relationship: formData.get("relationship"),
|
||
birth_date: formData.get("birth_date") || null,
|
||
is_spouse: formData.get("is_spouse") === "true",
|
||
is_disabled: formData.get("is_disabled") === "true",
|
||
income_amount: parseFloat(formData.get("income_amount")) || 0,
|
||
valid_from: formData.get("valid_from"),
|
||
valid_to: formData.get("valid_to") || null,
|
||
};
|
||
|
||
try {
|
||
const response = await fetch(
|
||
`${API_BASE}/payroll/employees/dependents/${dependentId}`,
|
||
{
|
||
method: "PUT",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(data),
|
||
}
|
||
);
|
||
|
||
if (response.ok) {
|
||
alert("扶養家族を更新しました");
|
||
document.getElementById("dependentFormModal").remove();
|
||
viewEmployee(currentEmployee.employee_id);
|
||
} else {
|
||
const error = await response.json();
|
||
alert("更新に失敗しました: " + error.detail);
|
||
}
|
||
} catch (error) {
|
||
alert("更新に失敗しました");
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// 初期化
|
||
loadEmployees();
|
||
</script>
|
||
</body>
|
||
</html>
|