給与管理システム新規作成
This commit is contained in:
434
backend/app/static/payroll-calculation.html
Normal file
434
backend/app/static/payroll-calculation.html
Normal file
@@ -0,0 +1,434 @@
|
||||
<!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>
|
||||
.filters {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.filters label {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.payroll-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.payroll-item {
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.payroll-item:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.status-draft {
|
||||
background: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
.status-calculated {
|
||||
background: #17a2b8;
|
||||
color: #fff;
|
||||
}
|
||||
.status-approved {
|
||||
background: #28a745;
|
||||
color: #fff;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 15px;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
margin-right: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
.payroll-detail {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.detail-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.detail-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.detail-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.total-row {
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
color: #007bff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>給与管理 - 月次給与計算</h1>
|
||||
|
||||
<div class="filters">
|
||||
<label>
|
||||
対象年月:
|
||||
<input type="number" id="filterYear" style="width: 100px;" placeholder="年">
|
||||
</label>
|
||||
<label>
|
||||
<input type="number" id="filterMonth" style="width: 80px;" min="1" max="12" placeholder="月">
|
||||
</label>
|
||||
<button class="btn btn-primary" onclick="loadPayrolls()">検索</button>
|
||||
<button class="btn btn-success" onclick="showCalculateForm()">新規計算</button>
|
||||
</div>
|
||||
|
||||
<div id="payrollList" class="payroll-list"></div>
|
||||
|
||||
<!-- 給与計算フォーム(モーダル風) -->
|
||||
<div id="calculateModal" style="display:none; 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="calculateForm" onsubmit="calculatePayroll(event)">
|
||||
<div class="form-group">
|
||||
<label>従業員 *</label>
|
||||
<select name="employee_id" id="employeeSelect" required>
|
||||
<option value="">選択してください</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>対象年 *</label>
|
||||
<input type="number" name="payroll_year" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>対象月 *</label>
|
||||
<input type="number" name="payroll_month" min="1" max="12" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>支給日 *</label>
|
||||
<input type="date" name="payment_date" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>勤務時間</label>
|
||||
<input type="number" name="working_hours" step="0.5" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>残業時間</label>
|
||||
<input type="number" name="overtime_hours" step="0.5" value="0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>休日出勤時間</label>
|
||||
<input type="number" name="holiday_hours" step="0.5" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>遅刻時間</label>
|
||||
<input type="number" name="late_hours" step="0.5" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>欠勤日数</label>
|
||||
<input type="number" name="absent_days" step="0.5" value="0">
|
||||
</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="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>住民税</label>
|
||||
<input type="number" name="resident_tax" step="0.01" value="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>その他控除</label>
|
||||
<input type="number" name="other_deduction" step="0.01" value="0">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">計算実行</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="closeCalculateForm()">キャンセル</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 給与明細表示 -->
|
||||
<div id="payrollDetail" class="payroll-detail" style="display:none;"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
|
||||
async function loadEmployees() {
|
||||
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('');
|
||||
}
|
||||
|
||||
async function loadPayrolls() {
|
||||
const year = document.getElementById('filterYear').value;
|
||||
const month = document.getElementById('filterMonth').value;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (year) params.append('payroll_year', year);
|
||||
if (month) params.append('payroll_month', month);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/calculation/?${params}`);
|
||||
const payrolls = await response.json();
|
||||
|
||||
const html = payrolls.map(p => `
|
||||
<div class="payroll-item" onclick="viewPayroll(${p.payroll_id})">
|
||||
<div>
|
||||
<strong>${p.payroll_year}年${p.payroll_month}月</strong>
|
||||
<span class="status-badge status-${p.status}">${getStatusLabel(p.status)}</span>
|
||||
</div>
|
||||
<div>
|
||||
従業員ID: ${p.employee_id} | 支給日: ${p.payment_date}
|
||||
</div>
|
||||
<div>
|
||||
<strong>差引支給額: ¥${Number(p.net_payment).toLocaleString()}</strong>
|
||||
(総支給: ¥${Number(p.total_payment).toLocaleString()} - 控除: ¥${Number(p.total_deduction).toLocaleString()})
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
document.getElementById('payrollList').innerHTML = html || '<p>給与データがありません</p>';
|
||||
} catch (error) {
|
||||
alert('給与一覧の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(status) {
|
||||
const labels = {
|
||||
'draft': '下書き',
|
||||
'calculated': '計算済み',
|
||||
'approved': '承認済み',
|
||||
'paid': '支払済み'
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
|
||||
function showCalculateForm() {
|
||||
document.getElementById('calculateModal').style.display = 'block';
|
||||
loadEmployees();
|
||||
|
||||
// デフォルト値設定
|
||||
const now = new Date();
|
||||
document.querySelector('[name="payroll_year"]').value = now.getFullYear();
|
||||
document.querySelector('[name="payroll_month"]').value = now.getMonth() + 1;
|
||||
}
|
||||
|
||||
function closeCalculateForm() {
|
||||
document.getElementById('calculateModal').style.display = 'none';
|
||||
document.getElementById('calculateForm').reset();
|
||||
}
|
||||
|
||||
async function calculatePayroll(event) {
|
||||
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/calculate`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
alert('給与計算が完了しました');
|
||||
closeCalculateForm();
|
||||
viewPayroll(result.payroll_id);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('計算に失敗しました: ' + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('計算に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function viewPayroll(payrollId) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}`);
|
||||
const payroll = await response.json();
|
||||
|
||||
const html = `
|
||||
<h2>${payroll.payroll_year}年${payroll.payroll_month}月 給与明細</h2>
|
||||
<p>従業員ID: ${payroll.employee_id} | 支給日: ${payroll.payment_date}</p>
|
||||
<p>ステータス: <span class="status-badge status-${payroll.status}">${getStatusLabel(payroll.status)}</span></p>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>勤怠情報</h3>
|
||||
<div class="detail-row"><span>出勤日数:</span><span>${payroll.working_days}日</span></div>
|
||||
<div class="detail-row"><span>勤務時間:</span><span>${payroll.working_hours}時間</span></div>
|
||||
<div class="detail-row"><span>残業時間:</span><span>${payroll.overtime_hours}時間</span></div>
|
||||
<div class="detail-row"><span>休日出勤:</span><span>${payroll.holiday_hours}時間</span></div>
|
||||
<div class="detail-row"><span>欠勤日数:</span><span>${payroll.absent_days}日</span></div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>支給項目</h3>
|
||||
<div class="detail-row"><span>基本給:</span><span>¥${Number(payroll.base_salary).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>残業手当:</span><span>¥${Number(payroll.overtime_pay).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>休日手当:</span><span>¥${Number(payroll.holiday_pay).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>通勤手当:</span><span>¥${Number(payroll.commute_allowance).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>その他手当:</span><span>¥${Number(payroll.other_allowance).toLocaleString()}</span></div>
|
||||
<div class="detail-row total-row"><span>総支給額:</span><span>¥${Number(payroll.total_payment).toLocaleString()}</span></div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>控除項目</h3>
|
||||
<div class="detail-row"><span>健康保険:</span><span>¥${Number(payroll.health_insurance).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>住民税:</span><span>¥${Number(payroll.resident_tax).toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>その他控除:</span><span>¥${Number(payroll.other_deduction).toLocaleString()}</span></div>
|
||||
<div class="detail-row total-row"><span>総控除額:</span><span>¥${Number(payroll.total_deduction).toLocaleString()}</span></div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section" style="background:#e7f3ff;">
|
||||
<div class="detail-row total-row" style="font-size:1.5em;">
|
||||
<span>差引支給額:</span>
|
||||
<span>¥${Number(payroll.net_payment).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${payroll.status === 'calculated' ? `
|
||||
<button class="btn btn-success" onclick="approvePayroll(${payroll.payroll_id})">承認</button>
|
||||
<button class="btn btn-primary" onclick="recalculatePayroll(${payroll.payroll_id})">再計算</button>
|
||||
` : ''}
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('payrollDetail').style.display='none'">閉じる</button>
|
||||
`;
|
||||
|
||||
document.getElementById('payrollDetail').innerHTML = html;
|
||||
document.getElementById('payrollDetail').style.display = 'block';
|
||||
document.getElementById('payrollDetail').scrollIntoView({ behavior: 'smooth' });
|
||||
} catch (error) {
|
||||
alert('給与明細の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function approvePayroll(payrollId) {
|
||||
if (!confirm('この給与を承認しますか?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}/approve`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ approved_by: 'admin' })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('承認しました');
|
||||
viewPayroll(payrollId);
|
||||
loadPayrolls();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('承認に失敗しました: ' + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('承認に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function recalculatePayroll(payrollId) {
|
||||
if (!confirm('給与を再計算しますか?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}/recalculate`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('再計算が完了しました');
|
||||
viewPayroll(payrollId);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('再計算に失敗しました: ' + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('再計算に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初期化
|
||||
const now = new Date();
|
||||
document.getElementById('filterYear').value = now.getFullYear();
|
||||
document.getElementById('filterMonth').value = now.getMonth() + 1;
|
||||
loadPayrolls();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
302
backend/app/static/payroll-employees.html
Normal file
302
backend/app/static/payroll-employees.html
Normal file
@@ -0,0 +1,302 @@
|
||||
<!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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<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>
|
||||
</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>
|
||||
</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="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>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
let currentEmployee = null;
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
// 空の値を除外
|
||||
Object.keys(data).forEach(key => {
|
||||
if (data[key] === '') {
|
||||
delete data[key];
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
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();
|
||||
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.email || '-'}</p>
|
||||
<p><strong>電話:</strong> ${employee.phone || '-'}</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>
|
||||
|
||||
<div class="dependent-list">
|
||||
<h3>扶養家族</h3>
|
||||
${employee.dependents.map(dep => `
|
||||
<div class="dependent-item">
|
||||
<strong>${dep.name}</strong> (${dep.relationship})
|
||||
${dep.birth_date ? ` - 生年月日: ${dep.birth_date}` : ''}
|
||||
${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>
|
||||
</div>
|
||||
`).join('') || '<p>扶養家族が登録されていません</p>'}
|
||||
</div>
|
||||
|
||||
<button class="btn btn-secondary" onclick="showTab('list')">戻る</button>
|
||||
`;
|
||||
|
||||
document.getElementById('employeeDetail').innerHTML = html;
|
||||
document.getElementById('detailTab').style.display = 'block';
|
||||
showTab('detail');
|
||||
} catch (error) {
|
||||
alert('従業員情報の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初期化
|
||||
loadEmployees();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
291
backend/app/static/payroll-salary-settings.html
Normal file
291
backend/app/static/payroll-salary-settings.html
Normal file
@@ -0,0 +1,291 @@
|
||||
<!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>
|
||||
326
backend/app/static/payroll-settings.html
Normal file
326
backend/app/static/payroll-settings.html
Normal file
@@ -0,0 +1,326 @@
|
||||
<!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;
|
||||
}
|
||||
.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="nav-tabs">
|
||||
<button class="nav-tab active" onclick="showTab('insurance')">保険料率</button>
|
||||
<button class="nav-tab" onclick="showTab('tax')">所得税率表</button>
|
||||
</div>
|
||||
|
||||
<!-- 保険料率タブ -->
|
||||
<div id="tab-insurance" class="tab-content active">
|
||||
<h2>社会保険料率設定</h2>
|
||||
<button class="btn btn-primary" onclick="showInsuranceForm()">新規追加</button>
|
||||
<div id="insuranceList"></div>
|
||||
|
||||
<div id="insuranceForm" style="display:none; margin-top:20px; padding:20px; border:2px solid #007bff; border-radius:5px;">
|
||||
<h3>保険料率の登録</h3>
|
||||
<form onsubmit="saveInsuranceRate(event)">
|
||||
<div class="form-group">
|
||||
<label>保険種類 *</label>
|
||||
<select name="rate_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>
|
||||
<input type="number" name="employee_rate" step="0.0001" required placeholder="例: 5.0 (5%)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>会社負担率 (%) *</label>
|
||||
<input type="number" name="employer_rate" step="0.0001" required placeholder="例: 5.0 (5%)">
|
||||
</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">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>備考</label>
|
||||
<input type="text" name="notes">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">登録</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideInsuranceForm()">キャンセル</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 所得税率表タブ -->
|
||||
<div id="tab-tax" class="tab-content">
|
||||
<h2>所得税率表</h2>
|
||||
<div class="form-group" style="max-width:300px;">
|
||||
<label>扶養人数でフィルター</label>
|
||||
<select id="filterDependents" onchange="loadIncomeTax()">
|
||||
<option value="">全て</option>
|
||||
<option value="0">0人</option>
|
||||
<option value="1">1人</option>
|
||||
<option value="2">2人</option>
|
||||
<option value="3">3人</option>
|
||||
<option value="4">4人</option>
|
||||
<option value="5">5人以上</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="document.getElementById('csvFile').click()">CSVインポート</button>
|
||||
<input type="file" id="csvFile" accept=".csv" style="display:none;" onchange="importTaxTable(event)">
|
||||
<div id="taxList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
|
||||
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 === 'insurance') {
|
||||
document.querySelectorAll('.nav-tab')[0].classList.add('active');
|
||||
document.getElementById('tab-insurance').classList.add('active');
|
||||
loadInsuranceRates();
|
||||
} else if (tabName === 'tax') {
|
||||
document.querySelectorAll('.nav-tab')[1].classList.add('active');
|
||||
document.getElementById('tab-tax').classList.add('active');
|
||||
loadIncomeTax();
|
||||
}
|
||||
}
|
||||
|
||||
function showInsuranceForm() {
|
||||
document.getElementById('insuranceForm').style.display = 'block';
|
||||
}
|
||||
|
||||
function hideInsuranceForm() {
|
||||
document.getElementById('insuranceForm').style.display = 'none';
|
||||
document.querySelector('#insuranceForm form').reset();
|
||||
}
|
||||
|
||||
async function loadInsuranceRates() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/insurance-rates`);
|
||||
const rates = await response.json();
|
||||
|
||||
const html = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>保険種類</th>
|
||||
<th>従業員負担率</th>
|
||||
<th>会社負担率</th>
|
||||
<th>適用期間</th>
|
||||
<th>備考</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rates.map(rate => `
|
||||
<tr>
|
||||
<td>${rate.rate_type}</td>
|
||||
<td>${(parseFloat(rate.employee_rate) * 100).toFixed(2)}%</td>
|
||||
<td>${(parseFloat(rate.employer_rate) * 100).toFixed(2)}%</td>
|
||||
<td>${rate.valid_from} ~ ${rate.valid_to || '現在'}</td>
|
||||
<td>${rate.notes || '-'}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('insuranceList').innerHTML = html;
|
||||
} catch (error) {
|
||||
alert('保険料率の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveInsuranceRate(event) {
|
||||
event.preventDefault();
|
||||
const form = event.target;
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
// パーセントを小数に変換
|
||||
data.employee_rate = parseFloat(data.employee_rate) / 100;
|
||||
data.employer_rate = parseFloat(data.employer_rate) / 100;
|
||||
|
||||
// 空の値を除外
|
||||
if (!data.valid_to) delete data.valid_to;
|
||||
if (!data.notes) delete data.notes;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/insurance-rates`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('保険料率を登録しました');
|
||||
hideInsuranceForm();
|
||||
loadInsuranceRates();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('登録に失敗しました: ' + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('登録に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadIncomeTax() {
|
||||
const dependentsFilter = document.getElementById('filterDependents').value;
|
||||
const params = dependentsFilter ? `?dependents_count=${dependentsFilter}` : '';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/income-tax${params}`);
|
||||
const taxes = await response.json();
|
||||
|
||||
const html = `
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>扶養人数</th>
|
||||
<th>月収From</th>
|
||||
<th>月収To</th>
|
||||
<th>税額</th>
|
||||
<th>適用期間</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${taxes.map(tax => `
|
||||
<tr>
|
||||
<td>${tax.dependents_count}人</td>
|
||||
<td>¥${Number(tax.monthly_income_from).toLocaleString()}</td>
|
||||
<td>¥${Number(tax.monthly_income_to).toLocaleString()}</td>
|
||||
<td>¥${Number(tax.tax_amount).toLocaleString()}</td>
|
||||
<td>${tax.valid_from} ~ ${tax.valid_to || '現在'}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('taxList').innerHTML = html;
|
||||
} catch (error) {
|
||||
alert('所得税率表の取得に失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function importTaxTable(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const validFrom = prompt('適用開始日を入力してください (YYYY-MM-DD):', new Date().toISOString().split('T')[0]);
|
||||
if (!validFrom) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/income-tax/import?valid_from=${validFrom}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
alert(result.message);
|
||||
loadIncomeTax();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('インポートに失敗しました: ' + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('インポートに失敗しました');
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
// 初期化
|
||||
loadInsuranceRates();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
99
backend/app/static/payroll.html
Normal file
99
backend/app/static/payroll.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<!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>
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
.menu-card {
|
||||
padding: 30px;
|
||||
border: 2px solid #007bff;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.menu-card:hover {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,123,255,0.3);
|
||||
}
|
||||
.menu-card h2 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.menu-card p {
|
||||
margin: 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.menu-card:hover p {
|
||||
opacity: 1;
|
||||
}
|
||||
.icon {
|
||||
font-size: 3em;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a href="../index.html" class="back-link">← メインメニューに戻る</a>
|
||||
|
||||
<h1>給与管理システム</h1>
|
||||
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
||||
|
||||
<div class="menu-grid">
|
||||
<div class="menu-card" onclick="location.href='payroll-employees.html'">
|
||||
<div class="icon">👥</div>
|
||||
<h2>従業員管理</h2>
|
||||
<p>従業員の基本情報、扶養家族情報を管理します</p>
|
||||
</div>
|
||||
|
||||
<div class="menu-card" onclick="location.href='payroll-salary-settings.html'">
|
||||
<div class="icon">💰</div>
|
||||
<h2>給与設定</h2>
|
||||
<p>各従業員の給与設定、基本給、手当などを管理します</p>
|
||||
</div>
|
||||
|
||||
<div class="menu-card" onclick="location.href='payroll-calculation.html'">
|
||||
<div class="icon">🧮</div>
|
||||
<h2>月次給与計算</h2>
|
||||
<p>毎月の給与計算、給与明細の作成・承認を行います</p>
|
||||
</div>
|
||||
|
||||
<div class="menu-card" onclick="location.href='payroll-settings.html'">
|
||||
<div class="icon">⚙️</div>
|
||||
<h2>税率・保険料設定</h2>
|
||||
<p>社会保険料率、所得税率表の管理を行います</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 50px; padding: 20px; background: #f0f8ff; border-radius: 5px;">
|
||||
<h3>📋 給与システムの使い方</h3>
|
||||
<ol>
|
||||
<li><strong>従業員管理</strong>で従業員を登録し、扶養家族情報を入力します</li>
|
||||
<li><strong>給与設定</strong>で各従業員の基本給や手当を設定します</li>
|
||||
<li><strong>税率・保険料設定</strong>で社会保険料率や所得税率表を確認・更新します</li>
|
||||
<li><strong>月次給与計算</strong>で勤怠情報を入力し、給与を計算・承認します</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user