This commit is contained in:
admin
2026-02-07 20:04:42 +09:00
parent 331953bb7f
commit c60cbf2d9a
29 changed files with 1851 additions and 1246 deletions

79
DIAGNOSIS.md Normal file
View File

@@ -0,0 +1,79 @@
# NAS上数据库连接问题诊断
## 当前配置
- **数据库地址**: 192.168.0.61:55432
- **数据库名**: njts_acct
- **用户**: njts_app
## 可能的原因
### 1. 网络连接问题
- [ ] 确认NAS是否在同一网络中
- [ ] 检查NAS是否能ping通数据库服务器 (192.168.0.61)
- [ ] 检查防火墙是否允许5432/55432端口访问
### 2. .env 文件缺失或配置不同
- [ ] 确认NAS上有 `.env` 文件
- [ ] 检查NAS上的 `.env` 文件配置是否正确
- [ ] 确认数据库服务器的IP地址在NAS所在网络中仍然有效
### 3. 容器网络问题
- [ ] 如果使用Docker确保容器网络配置正确
- [ ] 检查容器是否能解析数据库主机名/IP
## 排查命令
### 在NAS上执行以下命令
```bash
# 1. 测试网络连接
ping 192.168.0.61
# 2. 测试端口连接 (需要 psql 或 telnet)
psql -h 192.168.0.61 -p 55432 -U njts_app -d njts_acct
# 或使用 telnet如果可用
telnet 192.168.0.61 55432
# 3. 查看应用日志
docker logs njts_backend
# 4. 检查容器网络
docker network ls
docker network inspect njts_net
```
## 推荐解决方案
### 选项A: 使用主机名而不是IP推荐
编辑 `.env` 文件将IP地址改为主机名
```
DB_HOST=db-server # 或具体的主机名
DB_PORT=55432
```
需要确保DNS能解析该主机名。
### 选项B: 确认NAS网络配置
- 检查NAS的IP地址范围是否允许与数据库所在网络通信
- 可能需要调整网络路由或防火墙规则
### 选项C: 数据库服务器地址变化
- 如果数据库服务器的IP地址在NAS访问时不同需要更新 `.env`
- 例如可能需要用内网IP或DNS名称
## 立即检查
请告诉我:
1. NAS上是否能访问 192.168.0.61:55432?
2. 应用在NAS上产生的错误日志是什么?
3. 数据库服务器和NAS是否在同一网络中?

View File

@@ -11,8 +11,6 @@ def get_trial_balance(
date_from: Optional[str] = Query(None, description="YYYY-MM-DD"), date_from: Optional[str] = Query(None, description="YYYY-MM-DD"),
date_to: Optional[str] = Query(None, description="YYYY-MM-DD"), date_to: Optional[str] = Query(None, description="YYYY-MM-DD"),
): ):
print("trial-balance NEW VERSION (optional params)")
# 👉 没传参数时,给默认值(开发期非常推荐) # 👉 没传参数时,给默认值(开发期非常推荐)
if fiscal_year is None: if fiscal_year is None:
fiscal_year = 2025 fiscal_year = 2025
@@ -25,5 +23,4 @@ def get_trial_balance(
raise HTTPException(status_code=400, detail="期間指定が不正です。") raise HTTPException(status_code=400, detail="期間指定が不正です。")
result = fetch_trial_balance(date_from, date_to) result = fetch_trial_balance(date_from, date_to)
return result return result

View File

@@ -41,6 +41,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
""") """)
accounts = cur.fetchall() accounts = cur.fetchall()
# 科目データを格納 # 科目データを格納
account_data: Dict[str, Dict[str, Any]] = {} account_data: Dict[str, Dict[str, Any]] = {}
@@ -295,7 +296,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
total_credit = sum(data["credit"] for data in account_data.values()) total_credit = sum(data["credit"] for data in account_data.values())
total_closing = sum(data["closing_balance"] for data in account_data.values()) total_closing = sum(data["closing_balance"] for data in account_data.values())
return { result = {
"totals": { "totals": {
"opening_balance": str(total_opening), "opening_balance": str(total_opening),
"debit": str(total_debit), "debit": str(total_debit),
@@ -306,3 +307,5 @@ def fetch_trial_balance(date_from: str, date_to: str):
"accounts": result_accounts, "accounts": result_accounts,
"grand_total_closing": str(grand_total_closing) "grand_total_closing": str(grand_total_closing)
} }
return result

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -28,7 +28,7 @@
</table> </table>
<script> <script>
const API_BASE = "http://127.0.0.1:8080"; const API_BASE = "";
function formatYen(v) { function formatYen(v) {
return "¥ " + Number(v).toLocaleString("ja-JP"); return "¥ " + Number(v).toLocaleString("ja-JP");

View File

@@ -1,242 +1,333 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 月次給与計算</title> <title>給与管理 - 月次給与計算</title>
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css" />
<style> <style>
.filters { .filters {
margin-bottom: 20px; margin-bottom: 20px;
padding: 15px; padding: 15px;
background: #f9f9f9; background: #f9f9f9;
border-radius: 5px; border-radius: 5px;
} }
.filters label { .filters label {
margin-right: 15px; margin-right: 15px;
} }
.payroll-list { .payroll-list {
margin-top: 20px; margin-top: 20px;
} }
.payroll-item { .payroll-item {
padding: 15px; padding: 15px;
border: 1px solid #ddd; border: 1px solid #ddd;
margin-bottom: 10px; margin-bottom: 10px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
} }
.payroll-item:hover { .payroll-item:hover {
background: #f9f9f9; background: #f9f9f9;
} }
.status-badge { .status-badge {
padding: 4px 8px; padding: 4px 8px;
border-radius: 3px; border-radius: 3px;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
} }
.status-draft { .status-draft {
background: #ffc107; background: #ffc107;
color: #000; color: #000;
} }
.status-calculated { .status-calculated {
background: #17a2b8; background: #17a2b8;
color: #fff; color: #fff;
} }
.status-approved { .status-approved {
background: #28a745; background: #28a745;
color: #fff; color: #fff;
} }
.form-group { .form-group {
margin-bottom: 15px; margin-bottom: 15px;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
} }
.form-group input, .form-group select { .form-group input,
width: 100%; .form-group select {
padding: 8px; width: 100%;
border: 1px solid #ddd; padding: 8px;
border-radius: 4px; border: 1px solid #ddd;
} border-radius: 4px;
.form-row { }
display: grid; .form-row {
grid-template-columns: repeat(3, 1fr); display: grid;
gap: 15px; grid-template-columns: repeat(3, 1fr);
} gap: 15px;
.btn { }
padding: 10px 20px; .btn {
margin-right: 10px; padding: 10px 20px;
border: none; margin-right: 10px;
border-radius: 4px; border: none;
cursor: pointer; border-radius: 4px;
} cursor: pointer;
.btn-primary { }
background: #007bff; .btn-primary {
color: white; background: #007bff;
} color: white;
.btn-success { }
background: #28a745; .btn-success {
color: white; background: #28a745;
} color: white;
.btn-secondary { }
background: #6c757d; .btn-secondary {
color: white; background: #6c757d;
} color: white;
.payroll-detail { }
margin-top: 20px; .payroll-detail {
} margin-top: 20px;
.detail-section { }
margin-bottom: 20px; .detail-section {
padding: 15px; margin-bottom: 20px;
background: #f9f9f9; padding: 15px;
border-radius: 5px; background: #f9f9f9;
} border-radius: 5px;
.detail-row { }
display: flex; .detail-row {
justify-content: space-between; display: flex;
padding: 8px 0; justify-content: space-between;
border-bottom: 1px solid #ddd; padding: 8px 0;
} border-bottom: 1px solid #ddd;
.detail-row:last-child { }
border-bottom: none; .detail-row:last-child {
} border-bottom: none;
.total-row { }
font-weight: bold; .total-row {
font-size: 1.2em; font-weight: bold;
color: #007bff; font-size: 1.2em;
} color: #007bff;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>給与管理 - 月次給与計算</h1> <h1>給与管理 - 月次給与計算</h1>
<div class="filters"> <div class="filters">
<label> <label>
対象年月: 対象年月:
<input type="number" id="filterYear" style="width: 100px;" placeholder="年"> <input
</label> type="number"
<label> id="filterYear"
<input type="number" id="filterMonth" style="width: 80px;" min="1" max="12" placeholder="月"> style="width: 100px"
</label> placeholder="年"
<button class="btn btn-primary" onclick="loadPayrolls()">検索</button> />
<button class="btn btn-success" onclick="showCalculateForm()">新規計算</button> </label>
</div> <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="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;"> <div
<h2>給与計算</h2> id="calculateModal"
<form id="calculateForm" onsubmit="calculatePayroll(event)"> style="
<div class="form-group"> display: none;
<label>従業員 *</label> position: fixed;
<select name="employee_id" id="employeeSelect" required> top: 50px;
<option value="">選択してください</option> left: 50%;
</select> transform: translateX(-50%);
</div> 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-row">
<div class="form-group"> <div class="form-group">
<label>対象年 *</label> <label>対象年 *</label>
<input type="number" name="payroll_year" required> <input type="number" name="payroll_year" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>対象月 *</label> <label>対象月 *</label>
<input type="number" name="payroll_month" min="1" max="12" required> <input
</div> type="number"
<div class="form-group"> name="payroll_month"
<label>支給日 *</label> min="1"
<input type="date" name="payment_date" required> max="12"
</div> required
</div> />
</div>
<div class="form-group">
<label>支給日 *</label>
<input type="date" name="payment_date" required />
</div>
</div>
<div class="detail-section"> <div class="detail-section">
<h3>勤怠情報</h3> <h3>勤怠情報</h3>
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label>出勤日数</label> <label>出勤日数</label>
<input type="number" name="working_days" step="0.5" value="0"> <input type="number" name="working_days" step="0.5" value="0" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>勤務時間</label> <label>勤務時間</label>
<input type="number" name="working_hours" step="0.5" value="0"> <input
</div> type="number"
<div class="form-group"> name="working_hours"
<label>残業時間</label> step="0.5"
<input type="number" name="overtime_hours" step="0.5" value="0"> value="0"
</div> />
</div> </div>
<div class="form-row"> <div class="form-group">
<div class="form-group"> <label>残業時間</label>
<label>休日出勤時間</label> <input
<input type="number" name="holiday_hours" step="0.5" value="0"> type="number"
</div> name="overtime_hours"
<div class="form-group"> step="0.5"
<label>遅刻時間</label> value="0"
<input type="number" name="late_hours" step="0.5" value="0"> />
</div> </div>
<div class="form-group"> </div>
<label>欠勤日数</label> <div class="form-row">
<input type="number" name="absent_days" step="0.5" value="0"> <div class="form-group">
</div> <label>休日出勤時間</label>
</div> <input
</div> 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"> <div class="detail-section">
<h3>その他</h3> <h3>その他</h3>
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label>その他手当</label> <label>その他手当</label>
<input type="number" name="other_allowance" step="0.01" value="0"> <input
</div> type="number"
<div class="form-group"> name="other_allowance"
<label>住民税</label> step="0.01"
<input type="number" name="resident_tax" step="0.01" value="0"> value="0"
</div> />
<div class="form-group"> </div>
<label>その他控除</label> <div class="form-group">
<input type="number" name="other_deduction" step="0.01" value="0"> <label>住民税</label>
</div> <input
</div> type="number"
</div> 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="submit" class="btn btn-primary">計算実行</button>
<button type="button" class="btn btn-secondary" onclick="closeCalculateForm()">キャンセル</button> <button
</form> type="button"
</div> class="btn btn-secondary"
onclick="closeCalculateForm()"
>
キャンセル
</button>
</form>
</div>
<!-- 給与明細表示 --> <!-- 給与明細表示 -->
<div id="payrollDetail" class="payroll-detail" style="display:none;"></div> <div
id="payrollDetail"
class="payroll-detail"
style="display: none"
></div>
</div> </div>
<script> <script>
const API_BASE = 'http://localhost:8000'; const API_BASE = "";
async function loadEmployees() { async function loadEmployees() {
const response = await fetch(`${API_BASE}/payroll/employees/?is_active=true`); const response = await fetch(
const employees = await response.json(); `${API_BASE}/payroll/employees/?is_active=true`,
);
const employees = await response.json();
const select = document.getElementById('employeeSelect'); const select = document.getElementById("employeeSelect");
select.innerHTML = '<option value="">選択してください</option>' + select.innerHTML =
employees.map(emp => `<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`).join(''); '<option value="">選択してください</option>' +
} employees
.map(
(emp) =>
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`,
)
.join("");
}
async function loadPayrolls() { async function loadPayrolls() {
const year = document.getElementById('filterYear').value; const year = document.getElementById("filterYear").value;
const month = document.getElementById('filterMonth').value; const month = document.getElementById("filterMonth").value;
const params = new URLSearchParams(); const params = new URLSearchParams();
if (year) params.append('payroll_year', year); if (year) params.append("payroll_year", year);
if (month) params.append('payroll_month', month); if (month) params.append("payroll_month", month);
try { try {
const response = await fetch(`${API_BASE}/payroll/calculation/?${params}`); const response = await fetch(
const payrolls = await response.json(); `${API_BASE}/payroll/calculation/?${params}`,
);
const payrolls = await response.json();
const html = payrolls.map(p => ` const html = payrolls
.map(
(p) => `
<div class="payroll-item" onclick="viewPayroll(${p.payroll_id})"> <div class="payroll-item" onclick="viewPayroll(${p.payroll_id})">
<div> <div>
<strong>${p.payroll_year}${p.payroll_month}月</strong> <strong>${p.payroll_year}${p.payroll_month}月</strong>
@@ -250,78 +341,88 @@
(総支給: ¥${Number(p.total_payment).toLocaleString()} - 控除: ¥${Number(p.total_deduction).toLocaleString()}) (総支給: ¥${Number(p.total_payment).toLocaleString()} - 控除: ¥${Number(p.total_deduction).toLocaleString()})
</div> </div>
</div> </div>
`).join(''); `,
)
.join("");
document.getElementById('payrollList').innerHTML = html || '<p>給与データがありません</p>'; document.getElementById("payrollList").innerHTML =
} catch (error) { html || "<p>給与データがありません</p>";
alert('給与一覧の取得に失敗しました'); } catch (error) {
console.error(error); alert("給与一覧の取得に失敗しました");
} console.error(error);
} }
}
function getStatusLabel(status) { function getStatusLabel(status) {
const labels = { const labels = {
'draft': '下書き', draft: "下書き",
'calculated': '計算済み', calculated: "計算済み",
'approved': '承認済み', approved: "承認済み",
'paid': '支払済み' paid: "支払済み",
}; };
return labels[status] || status; 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);
} }
}
function showCalculateForm() { async function viewPayroll(payrollId) {
document.getElementById('calculateModal').style.display = 'block'; try {
loadEmployees(); const response = await fetch(
`${API_BASE}/payroll/calculation/${payrollId}`,
);
const payroll = await response.json();
// デフォルト値設定 const html = `
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> <h2>${payroll.payroll_year}${payroll.payroll_month}月 給与明細</h2>
<p>従業員ID: ${payroll.employee_id} | 支給日: ${payroll.payment_date}</p> <p>従業員ID: ${payroll.employee_id} | 支給日: ${payroll.payment_date}</p>
<p>ステータス: <span class="status-badge status-${payroll.status}">${getStatusLabel(payroll.status)}</span></p> <p>ステータス: <span class="status-badge status-${payroll.status}">${getStatusLabel(payroll.status)}</span></p>
@@ -363,72 +464,84 @@
</div> </div>
</div> </div>
${payroll.status === 'calculated' ? ` ${
payroll.status === "calculated"
? `
<button class="btn btn-success" onclick="approvePayroll(${payroll.payroll_id})">承認</button> <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-primary" onclick="recalculatePayroll(${payroll.payroll_id})">再計算</button>
` : ''} `
: ""
}
<button class="btn btn-secondary" onclick="document.getElementById('payrollDetail').style.display='none'">閉じる</button> <button class="btn btn-secondary" onclick="document.getElementById('payrollDetail').style.display='none'">閉じる</button>
`; `;
document.getElementById('payrollDetail').innerHTML = html; document.getElementById("payrollDetail").innerHTML = html;
document.getElementById('payrollDetail').style.display = 'block'; document.getElementById("payrollDetail").style.display = "block";
document.getElementById('payrollDetail').scrollIntoView({ behavior: 'smooth' }); document
} catch (error) { .getElementById("payrollDetail")
alert('給与明細の取得に失敗しました'); .scrollIntoView({ behavior: "smooth" });
console.error(error); } catch (error) {
} alert("給与明細の取得に失敗しました");
console.error(error);
} }
}
async function approvePayroll(payrollId) { async function approvePayroll(payrollId) {
if (!confirm('この給与を承認しますか?')) return; if (!confirm("この給与を承認しますか?")) return;
try { try {
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}/approve`, { const response = await fetch(
method: 'POST', `${API_BASE}/payroll/calculation/${payrollId}/approve`,
headers: {'Content-Type': 'application/json'}, {
body: JSON.stringify({ approved_by: 'admin' }) method: "POST",
}); headers: { "Content-Type": "application/json" },
body: JSON.stringify({ approved_by: "admin" }),
},
);
if (response.ok) { if (response.ok) {
alert('承認しました'); alert("承認しました");
viewPayroll(payrollId); viewPayroll(payrollId);
loadPayrolls(); loadPayrolls();
} else { } else {
const error = await response.json(); const error = await response.json();
alert('承認に失敗しました: ' + error.detail); alert("承認に失敗しました: " + error.detail);
} }
} catch (error) { } catch (error) {
alert('承認に失敗しました'); alert("承認に失敗しました");
console.error(error); console.error(error);
}
} }
}
async function recalculatePayroll(payrollId) { async function recalculatePayroll(payrollId) {
if (!confirm('給与を再計算しますか?')) return; if (!confirm("給与を再計算しますか?")) return;
try { try {
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}/recalculate`, { const response = await fetch(
method: 'POST' `${API_BASE}/payroll/calculation/${payrollId}/recalculate`,
}); {
method: "POST",
},
);
if (response.ok) { if (response.ok) {
alert('再計算が完了しました'); alert("再計算が完了しました");
viewPayroll(payrollId); viewPayroll(payrollId);
} else { } else {
const error = await response.json(); const error = await response.json();
alert('再計算に失敗しました: ' + error.detail); alert("再計算に失敗しました: " + error.detail);
} }
} catch (error) { } catch (error) {
alert('再計算に失敗しました'); alert("再計算に失敗しました");
console.error(error); console.error(error);
}
} }
}
// 初期化 // 初期化
const now = new Date(); const now = new Date();
document.getElementById('filterYear').value = now.getFullYear(); document.getElementById("filterYear").value = now.getFullYear();
document.getElementById('filterMonth').value = now.getMonth() + 1; document.getElementById("filterMonth").value = now.getMonth() + 1;
loadPayrolls(); loadPayrolls();
</script> </script>
</body> </body>
</html> </html>

View File

@@ -1,302 +1,341 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 従業員管理</title> <title>給与管理 - 従業員管理</title>
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css" />
<style> <style>
.nav-tabs { .nav-tabs {
display: flex; display: flex;
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 20px;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
} }
.nav-tab { .nav-tab {
padding: 10px 20px; padding: 10px 20px;
cursor: pointer; cursor: pointer;
border: none; border: none;
background: #f0f0f0; background: #f0f0f0;
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
} }
.nav-tab.active { .nav-tab.active {
background: #007bff; background: #007bff;
color: white; color: white;
} }
.tab-content { .tab-content {
display: none; display: none;
} }
.tab-content.active { .tab-content.active {
display: block; display: block;
} }
.employee-list { .employee-list {
margin-top: 20px; margin-top: 20px;
} }
.employee-item { .employee-item {
padding: 15px; padding: 15px;
border: 1px solid #ddd; border: 1px solid #ddd;
margin-bottom: 10px; margin-bottom: 10px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
} }
.employee-item:hover { .employee-item:hover {
background: #f9f9f9; background: #f9f9f9;
} }
.form-group { .form-group {
margin-bottom: 15px; margin-bottom: 15px;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
} }
.form-group input, .form-group select, .form-group textarea { .form-group input,
width: 100%; .form-group select,
padding: 8px; .form-group textarea {
border: 1px solid #ddd; width: 100%;
border-radius: 4px; padding: 8px;
} border: 1px solid #ddd;
.btn { border-radius: 4px;
padding: 10px 20px; }
margin-right: 10px; .btn {
border: none; padding: 10px 20px;
border-radius: 4px; margin-right: 10px;
cursor: pointer; border: none;
} border-radius: 4px;
.btn-primary { cursor: pointer;
background: #007bff; }
color: white; .btn-primary {
} background: #007bff;
.btn-secondary { color: white;
background: #6c757d; }
color: white; .btn-secondary {
} background: #6c757d;
.btn-danger { color: white;
background: #dc3545; }
color: white; .btn-danger {
} background: #dc3545;
.dependent-list { color: white;
margin-top: 20px; }
padding: 15px; .dependent-list {
background: #f9f9f9; margin-top: 20px;
border-radius: 5px; padding: 15px;
} background: #f9f9f9;
.dependent-item { border-radius: 5px;
padding: 10px; }
border: 1px solid #ddd; .dependent-item {
margin-bottom: 10px; padding: 10px;
background: white; border: 1px solid #ddd;
border-radius: 4px; margin-bottom: 10px;
} background: white;
border-radius: 4px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>給与管理 - 従業員管理</h1> <h1>給与管理 - 従業員管理</h1>
<div class="nav-tabs"> <div class="nav-tabs">
<button class="nav-tab active" onclick="showTab('list')">従業員一覧</button> <button class="nav-tab active" onclick="showTab('list')">
<button class="nav-tab" onclick="showTab('create')">新規登録</button> 従業員一覧
<button class="nav-tab" onclick="showTab('detail')" id="detailTab" style="display:none;">従業員詳細</button> </button>
</div> <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 id="tab-list" class="tab-content active">
<div class="filters"> <div class="filters">
<label> <label>
<input type="checkbox" id="filterActive" checked onchange="loadEmployees()"> <input
在職中のみ表示 type="checkbox"
</label> id="filterActive"
</div> checked
<div id="employeeList" class="employee-list"></div> onchange="loadEmployees()"
/>
在職中のみ表示
</label>
</div> </div>
<div id="employeeList" class="employee-list"></div>
</div>
<!-- 新規登録タブ --> <!-- 新規登録タブ -->
<div id="tab-create" class="tab-content"> <div id="tab-create" class="tab-content">
<h2>従業員の新規登録</h2> <h2>従業員の新規登録</h2>
<form id="createForm" onsubmit="createEmployee(event)"> <form id="createForm" onsubmit="createEmployee(event)">
<div class="form-group"> <div class="form-group">
<label>従業員コード *</label> <label>従業員コード *</label>
<input type="text" name="employee_code" required> <input type="text" name="employee_code" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>氏名 *</label> <label>氏名 *</label>
<input type="text" name="name" required> <input type="text" name="name" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>フリガナ</label> <label>フリガナ</label>
<input type="text" name="name_kana"> <input type="text" name="name_kana" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>メールアドレス</label> <label>メールアドレス</label>
<input type="email" name="email"> <input type="email" name="email" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>電話番号</label> <label>電話番号</label>
<input type="tel" name="phone"> <input type="tel" name="phone" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>入社日 *</label> <label>入社日 *</label>
<input type="date" name="hire_date" required> <input type="date" name="hire_date" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>銀行名</label> <label>銀行名</label>
<input type="text" name="bank_name"> <input type="text" name="bank_name" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>支店名</label> <label>支店名</label>
<input type="text" name="bank_branch"> <input type="text" name="bank_branch" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>口座番号</label> <label>口座番号</label>
<input type="text" name="bank_account_number"> <input type="text" name="bank_account_number" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label>口座種別</label> <label>口座種別</label>
<select name="bank_account_type"> <select name="bank_account_type">
<option value="">選択してください</option> <option value="">選択してください</option>
<option value="普通">普通</option> <option value="普通">普通</option>
<option value="当座">当座</option> <option value="当座">当座</option>
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label>備考</label> <label>備考</label>
<textarea name="notes" rows="3"></textarea> <textarea name="notes" rows="3"></textarea>
</div> </div>
<button type="submit" class="btn btn-primary">登録</button> <button type="submit" class="btn btn-primary">登録</button>
<button type="button" class="btn btn-secondary" onclick="showTab('list')">キャンセル</button> <button
</form> type="button"
</div> class="btn btn-secondary"
onclick="showTab('list')"
>
キャンセル
</button>
</form>
</div>
<!-- 従業員詳細タブ --> <!-- 従業員詳細タブ -->
<div id="tab-detail" class="tab-content"> <div id="tab-detail" class="tab-content">
<div id="employeeDetail"></div> <div id="employeeDetail"></div>
</div> </div>
</div> </div>
<script> <script>
const API_BASE = 'http://localhost:8000'; const API_BASE = "";
let currentEmployee = null; let currentEmployee = null;
function showTab(tabName) { function showTab(tabName) {
document.querySelectorAll('.nav-tab').forEach(tab => tab.classList.remove('active')); document
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); .querySelectorAll(".nav-tab")
.forEach((tab) => tab.classList.remove("active"));
document
.querySelectorAll(".tab-content")
.forEach((content) => content.classList.remove("active"));
if (tabName === 'list') { if (tabName === "list") {
document.querySelector('.nav-tab').classList.add('active'); document.querySelector(".nav-tab").classList.add("active");
document.getElementById('tab-list').classList.add('active'); document.getElementById("tab-list").classList.add("active");
loadEmployees(); loadEmployees();
} else if (tabName === 'create') { } else if (tabName === "create") {
document.querySelectorAll('.nav-tab')[1].classList.add('active'); document.querySelectorAll(".nav-tab")[1].classList.add("active");
document.getElementById('tab-create').classList.add('active'); document.getElementById("tab-create").classList.add("active");
} else if (tabName === 'detail') { } else if (tabName === "detail") {
document.getElementById('detailTab').classList.add('active'); document.getElementById("detailTab").classList.add("active");
document.getElementById('tab-detail').classList.add('active'); document.getElementById("tab-detail").classList.add("active");
}
} }
}
async function loadEmployees() { async function loadEmployees() {
const isActive = document.getElementById('filterActive').checked; const isActive = document.getElementById("filterActive").checked;
const url = `${API_BASE}/payroll/employees/${isActive ? '?is_active=true' : ''}`; const url = `${API_BASE}/payroll/employees/${isActive ? "?is_active=true" : ""}`;
try { try {
const response = await fetch(url); const response = await fetch(url);
const employees = await response.json(); const employees = await response.json();
const html = employees.map(emp => ` const html = employees
.map(
(emp) => `
<div class="employee-item" onclick="viewEmployee(${emp.employee_id})"> <div class="employee-item" onclick="viewEmployee(${emp.employee_id})">
<strong>${emp.employee_code}</strong> - ${emp.name} <strong>${emp.employee_code}</strong> - ${emp.name}
${emp.name_kana ? `(${emp.name_kana})` : ''} ${emp.name_kana ? `(${emp.name_kana})` : ""}
<br> <br>
<small>入社日: ${emp.hire_date} ${emp.termination_date ? ` | 退職日: ${emp.termination_date}` : ''}</small> <small>入社日: ${emp.hire_date} ${emp.termination_date ? ` | 退職日: ${emp.termination_date}` : ""}</small>
</div> </div>
`).join(''); `,
)
.join("");
document.getElementById('employeeList').innerHTML = html || '<p>従業員が登録されていません</p>'; document.getElementById("employeeList").innerHTML =
} catch (error) { html || "<p>従業員が登録されていません</p>";
alert('従業員一覧の取得に失敗しました'); } catch (error) {
console.error(error); alert("従業員一覧の取得に失敗しました");
} console.error(error);
} }
}
async function createEmployee(event) { async function createEmployee(event) {
event.preventDefault(); event.preventDefault();
const form = event.target; const form = event.target;
const formData = new FormData(form); const formData = new FormData(form);
const data = Object.fromEntries(formData.entries()); const data = Object.fromEntries(formData.entries());
// 空の値を除外 // 空の値を除外
Object.keys(data).forEach(key => { Object.keys(data).forEach((key) => {
if (data[key] === '') { if (data[key] === "") {
delete data[key]; delete data[key];
} }
}); });
try { try {
const response = await fetch(`${API_BASE}/payroll/employees/`, { const response = await fetch(`${API_BASE}/payroll/employees/`, {
method: 'POST', method: "POST",
headers: {'Content-Type': 'application/json'}, headers: { "Content-Type": "application/json" },
body: JSON.stringify(data) body: JSON.stringify(data),
}); });
if (response.ok) { if (response.ok) {
alert('従業員を登録しました'); alert("従業員を登録しました");
form.reset(); form.reset();
showTab('list'); showTab("list");
} else { } else {
const error = await response.json(); const error = await response.json();
alert('登録に失敗しました: ' + error.detail); alert("登録に失敗しました: " + error.detail);
} }
} catch (error) { } catch (error) {
alert('登録に失敗しました'); alert("登録に失敗しました");
console.error(error); console.error(error);
}
} }
}
async function viewEmployee(employeeId) { async function viewEmployee(employeeId) {
try { try {
const response = await fetch(`${API_BASE}/payroll/employees/${employeeId}`); const response = await fetch(
const employee = await response.json(); `${API_BASE}/payroll/employees/${employeeId}`,
currentEmployee = employee; );
const employee = await response.json();
currentEmployee = employee;
const html = ` const html = `
<h2>${employee.name} (${employee.employee_code})</h2> <h2>${employee.name} (${employee.employee_code})</h2>
<p><strong>フリガナ:</strong> ${employee.name_kana || '-'}</p> <p><strong>フリガナ:</strong> ${employee.name_kana || "-"}</p>
<p><strong>メール:</strong> ${employee.email || '-'}</p> <p><strong>メール:</strong> ${employee.email || "-"}</p>
<p><strong>電話:</strong> ${employee.phone || '-'}</p> <p><strong>電話:</strong> ${employee.phone || "-"}</p>
<p><strong>入社日:</strong> ${employee.hire_date}</p> <p><strong>入社日:</strong> ${employee.hire_date}</p>
<p><strong>退職日:</strong> ${employee.termination_date || '-'}</p> <p><strong>退職日:</strong> ${employee.termination_date || "-"}</p>
<p><strong>銀行:</strong> ${employee.bank_name || '-'} ${employee.bank_branch || ''}</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.bank_account_type || ""} ${employee.bank_account_number || "-"}</p>
<p><strong>備考:</strong> ${employee.notes || '-'}</p> <p><strong>備考:</strong> ${employee.notes || "-"}</p>
<div class="dependent-list"> <div class="dependent-list">
<h3>扶養家族</h3> <h3>扶養家族</h3>
${employee.dependents.map(dep => ` ${
employee.dependents
.map(
(dep) => `
<div class="dependent-item"> <div class="dependent-item">
<strong>${dep.name}</strong> (${dep.relationship}) <strong>${dep.name}</strong> (${dep.relationship})
${dep.birth_date ? ` - 生年月日: ${dep.birth_date}` : ''} ${dep.birth_date ? ` - 生年月日: ${dep.birth_date}` : ""}
${dep.is_spouse ? ' <span style="color:blue;">[配偶者]</span>' : ''} ${dep.is_spouse ? ' <span style="color:blue;">[配偶者]</span>' : ""}
${dep.is_disabled ? ' <span style="color:red;">[障害者]</span>' : ''} ${dep.is_disabled ? ' <span style="color:red;">[障害者]</span>' : ""}
<br><small>有効期間: ${dep.valid_from} ${dep.valid_to || '現在'}</small> <br><small>有効期間: ${dep.valid_from} ${dep.valid_to || "現在"}</small>
</div> </div>
`).join('') || '<p>扶養家族が登録されていません</p>'} `,
)
.join("") || "<p>扶養家族が登録されていません</p>"
}
</div> </div>
<button class="btn btn-secondary" onclick="showTab('list')">戻る</button> <button class="btn btn-secondary" onclick="showTab('list')">戻る</button>
`; `;
document.getElementById('employeeDetail').innerHTML = html; document.getElementById("employeeDetail").innerHTML = html;
document.getElementById('detailTab').style.display = 'block'; document.getElementById("detailTab").style.display = "block";
showTab('detail'); showTab("detail");
} catch (error) { } catch (error) {
alert('従業員情報の取得に失敗しました'); alert("従業員情報の取得に失敗しました");
console.error(error); console.error(error);
}
} }
}
// 初期化 // 初期化
loadEmployees(); loadEmployees();
</script> </script>
</body> </body>
</html> </html>

View File

@@ -1,171 +1,218 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 給与設定</title> <title>給与管理 - 給与設定</title>
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css" />
<style> <style>
.employee-select { .employee-select {
margin-bottom: 20px; margin-bottom: 20px;
padding: 15px; padding: 15px;
background: #f9f9f9; background: #f9f9f9;
border-radius: 5px; border-radius: 5px;
} }
.form-group { .form-group {
margin-bottom: 15px; margin-bottom: 15px;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
} }
.form-group input, .form-group select { .form-group input,
width: 100%; .form-group select {
padding: 8px; width: 100%;
border: 1px solid #ddd; padding: 8px;
border-radius: 4px; border: 1px solid #ddd;
} border-radius: 4px;
.btn { }
padding: 10px 20px; .btn {
margin-right: 10px; padding: 10px 20px;
border: none; margin-right: 10px;
border-radius: 4px; border: none;
cursor: pointer; border-radius: 4px;
} cursor: pointer;
.btn-primary { }
background: #007bff; .btn-primary {
color: white; background: #007bff;
} color: white;
.btn-secondary { }
background: #6c757d; .btn-secondary {
color: white; background: #6c757d;
} color: white;
table { }
width: 100%; table {
border-collapse: collapse; width: 100%;
margin-top: 20px; border-collapse: collapse;
} margin-top: 20px;
table th, table td { }
border: 1px solid #ddd; table th,
padding: 10px; table td {
text-align: left; border: 1px solid #ddd;
} padding: 10px;
table th { text-align: left;
background: #f0f0f0; }
} table th {
background: #f0f0f0;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>給与管理 - 給与設定</h1> <h1>給与管理 - 給与設定</h1>
<div class="employee-select"> <div class="employee-select">
<label><strong>従業員を選択:</strong></label> <label><strong>従業員を選択:</strong></label>
<select id="employeeSelect" onchange="loadSalarySettings()"> <select id="employeeSelect" onchange="loadSalarySettings()">
<option value="">従業員を選択してください</option> <option value="">従業員を選択してください</option>
</select> </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>
<div id="settingsContent" style="display:none;"> <div
<h2>給与設定履歴</h2> id="currentSetting"
<button class="btn btn-primary" onclick="showAddForm()">新規設定追加</button> style="
<div id="settingsList"></div> margin-top: 30px;
padding: 20px;
<div id="addForm" style="display:none; margin-top:20px; padding:20px; border:2px solid #007bff; border-radius:5px;"> background: #e7f3ff;
<h3>給与設定の追加</h3> border-radius: 5px;
<form onsubmit="saveSalarySetting(event)"> "
<div class="form-group"> >
<label>基本給 (円) *</label> <h3>現在の給与設定</h3>
<input type="number" name="base_salary" step="0.01" required> <div id="currentSettingContent"></div>
</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>
</div>
</div> </div>
<script> <script>
const API_BASE = 'http://localhost:8000'; const API_BASE = "";
let currentEmployeeId = null; let currentEmployeeId = null;
async function loadEmployees() { async function loadEmployees() {
try { try {
const response = await fetch(`${API_BASE}/payroll/employees/?is_active=true`); const response = await fetch(
const employees = await response.json(); `${API_BASE}/payroll/employees/?is_active=true`,
);
const employees = await response.json();
const select = document.getElementById('employeeSelect'); const select = document.getElementById("employeeSelect");
select.innerHTML = '<option value="">従業員を選択してください</option>' + select.innerHTML =
employees.map(emp => `<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`).join(''); '<option value="">従業員を選択してください</option>' +
} catch (error) { employees
alert('従業員一覧の取得に失敗しました'); .map(
console.error(error); (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;
} }
async function loadSalarySettings() { currentEmployeeId = employeeId;
const employeeId = document.getElementById('employeeSelect').value; document.getElementById("settingsContent").style.display = "block";
if (!employeeId) {
document.getElementById('settingsContent').style.display = 'none';
return;
}
currentEmployeeId = employeeId; try {
document.getElementById('settingsContent').style.display = 'block'; // 給与設定履歴を取得
const response = await fetch(
`${API_BASE}/payroll/settings/salary/${employeeId}`,
);
const settings = await response.json();
try { const html = `
// 給与設定履歴を取得
const response = await fetch(`${API_BASE}/payroll/settings/salary/${employeeId}`);
const settings = await response.json();
const html = `
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -179,113 +226,123 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${settings.map(s => ` ${settings
.map(
(s) => `
<tr> <tr>
<td>${s.valid_from} ${s.valid_to || '現在'}</td> <td>${s.valid_from} ${s.valid_to || "現在"}</td>
<td>¥${Number(s.base_salary).toLocaleString()}</td> <td>¥${Number(s.base_salary).toLocaleString()}</td>
<td>${s.hourly_rate ? '¥' + Number(s.hourly_rate).toLocaleString() : '-'}</td> <td>${s.hourly_rate ? "¥" + Number(s.hourly_rate).toLocaleString() : "-"}</td>
<td>${s.employment_type}</td> <td>${s.employment_type}</td>
<td>${s.payment_type}</td> <td>${s.payment_type}</td>
<td>¥${Number(s.commute_allowance).toLocaleString()}</td> <td>¥${Number(s.commute_allowance).toLocaleString()}</td>
<td>¥${Number(s.other_allowance).toLocaleString()}</td> <td>¥${Number(s.other_allowance).toLocaleString()}</td>
</tr> </tr>
`).join('')} `,
)
.join("")}
</tbody> </tbody>
</table> </table>
`; `;
document.getElementById('settingsList').innerHTML = html; document.getElementById("settingsList").innerHTML = html;
// 現在の設定を取得 // 現在の設定を取得
loadCurrentSetting(employeeId); loadCurrentSetting(employeeId);
} catch (error) { } catch (error) {
alert('給与設定の取得に失敗しました'); alert("給与設定の取得に失敗しました");
console.error(error); console.error(error);
}
} }
}
async function loadCurrentSetting(employeeId) { async function loadCurrentSetting(employeeId) {
try { try {
const response = await fetch(`${API_BASE}/payroll/settings/salary/${employeeId}/current`); const response = await fetch(
if (response.ok) { `${API_BASE}/payroll/settings/salary/${employeeId}/current`,
const setting = await response.json(); );
if (response.ok) {
const setting = await response.json();
const html = ` const html = `
<div style="display:grid; grid-template-columns: repeat(2, 1fr); gap:15px;"> <div style="display:grid; grid-template-columns: repeat(2, 1fr); gap:15px;">
<div><strong>基本給:</strong> ¥${Number(setting.base_salary).toLocaleString()}</div> <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.hourly_rate ? "¥" + Number(setting.hourly_rate).toLocaleString() : "-"}</div>
<div><strong>雇用形態:</strong> ${setting.employment_type}</div> <div><strong>雇用形態:</strong> ${setting.employment_type}</div>
<div><strong>支給形態:</strong> ${setting.payment_type}</div> <div><strong>支給形態:</strong> ${setting.payment_type}</div>
<div><strong>通勤手当:</strong> ¥${Number(setting.commute_allowance).toLocaleString()}</div> <div><strong>通勤手当:</strong> ¥${Number(setting.commute_allowance).toLocaleString()}</div>
<div><strong>その他手当:</strong> ¥${Number(setting.other_allowance).toLocaleString()}</div> <div><strong>その他手当:</strong> ¥${Number(setting.other_allowance).toLocaleString()}</div>
<div><strong>適用期間:</strong> ${setting.valid_from} ${setting.valid_to || '現在'}</div> <div><strong>適用期間:</strong> ${setting.valid_from} ${setting.valid_to || "現在"}</div>
</div> </div>
`; `;
document.getElementById('currentSettingContent').innerHTML = html; document.getElementById("currentSettingContent").innerHTML = html;
} else { } else {
document.getElementById('currentSettingContent').innerHTML = '<p>給与設定が登録されていません</p>'; document.getElementById("currentSettingContent").innerHTML =
} "<p>給与設定が登録されていません</p>";
} catch (error) { }
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;
} }
function showAddForm() { if (!data.valid_to) {
document.getElementById('addForm').style.display = 'block'; delete data.valid_to;
// デフォルト値設定
document.querySelector('[name="valid_from"]').value = new Date().toISOString().split('T')[0];
} }
function hideAddForm() { try {
document.getElementById('addForm').style.display = 'none'; const response = await fetch(`${API_BASE}/payroll/settings/salary`, {
document.querySelector('#addForm form').reset(); 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);
} }
}
async function saveSalarySetting(event) { // 初期化
event.preventDefault(); loadEmployees();
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> </script>
</body> </body>
</html> </html>

View File

@@ -1,186 +1,236 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理 - 設定</title> <title>給与管理 - 設定</title>
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css" />
<style> <style>
.nav-tabs { .nav-tabs {
display: flex; display: flex;
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 20px;
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
} }
.nav-tab { .nav-tab {
padding: 10px 20px; padding: 10px 20px;
cursor: pointer; cursor: pointer;
border: none; border: none;
background: #f0f0f0; background: #f0f0f0;
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
} }
.nav-tab.active { .nav-tab.active {
background: #007bff; background: #007bff;
color: white; color: white;
} }
.tab-content { .tab-content {
display: none; display: none;
} }
.tab-content.active { .tab-content.active {
display: block; display: block;
} }
.form-group { .form-group {
margin-bottom: 15px; margin-bottom: 15px;
} }
.form-group label { .form-group label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
font-weight: bold; font-weight: bold;
} }
.form-group input, .form-group select { .form-group input,
width: 100%; .form-group select {
padding: 8px; width: 100%;
border: 1px solid #ddd; padding: 8px;
border-radius: 4px; border: 1px solid #ddd;
} border-radius: 4px;
.btn { }
padding: 10px 20px; .btn {
margin-right: 10px; padding: 10px 20px;
border: none; margin-right: 10px;
border-radius: 4px; border: none;
cursor: pointer; border-radius: 4px;
} cursor: pointer;
.btn-primary { }
background: #007bff; .btn-primary {
color: white; background: #007bff;
} color: white;
.btn-secondary { }
background: #6c757d; .btn-secondary {
color: white; background: #6c757d;
} color: white;
table { }
width: 100%; table {
border-collapse: collapse; width: 100%;
margin-top: 20px; border-collapse: collapse;
} margin-top: 20px;
table th, table td { }
border: 1px solid #ddd; table th,
padding: 10px; table td {
text-align: left; border: 1px solid #ddd;
} padding: 10px;
table th { text-align: left;
background: #f0f0f0; }
} table th {
background: #f0f0f0;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>給与管理 - 設定</h1> <h1>給与管理 - 設定</h1>
<div class="nav-tabs"> <div class="nav-tabs">
<button class="nav-tab active" onclick="showTab('insurance')">保険料率</button> <button class="nav-tab active" onclick="showTab('insurance')">
<button class="nav-tab" onclick="showTab('tax')">所得税率表</button> 保険料率
</div> </button>
<button class="nav-tab" onclick="showTab('tax')">所得税率表</button>
</div>
<!-- 保険料率タブ --> <!-- 保険料率タブ -->
<div id="tab-insurance" class="tab-content active"> <div id="tab-insurance" class="tab-content active">
<h2>社会保険料率設定</h2> <h2>社会保険料率設定</h2>
<button class="btn btn-primary" onclick="showInsuranceForm()">新規追加</button> <button class="btn btn-primary" onclick="showInsuranceForm()">
<div id="insuranceList"></div> 新規追加
</button>
<div id="insuranceList"></div>
<div id="insuranceForm" style="display:none; margin-top:20px; padding:20px; border:2px solid #007bff; border-radius:5px;"> <div
<h3>保険料率の登録</h3> id="insuranceForm"
<form onsubmit="saveInsuranceRate(event)"> style="
<div class="form-group"> display: none;
<label>保険種類 *</label> margin-top: 20px;
<select name="rate_type" required> padding: 20px;
<option value="">選択してください</option> border: 2px solid #007bff;
<option value="健康保険">健康保険</option> border-radius: 5px;
<option value="厚生年金">厚生年金</option> "
<option value="雇用保険">雇用保険</option> >
<option value="労災保険">労災保険</option> <h3>保険料率の登録</h3>
</select> <form onsubmit="saveInsuranceRate(event)">
</div> <div class="form-group">
<div class="form-group"> <label>保険種類 *</label>
<label>従業員負担率 (%) *</label> <select name="rate_type" required>
<input type="number" name="employee_rate" step="0.0001" required placeholder="例: 5.0 (5%)"> <option value="">選択してください</option>
</div> <option value="健康保険">健康保険</option>
<div class="form-group"> <option value="厚生年金">厚生年金</option>
<label>会社負担率 (%) *</label> <option value="雇用保険">雇用保険</option>
<input type="number" name="employer_rate" step="0.0001" required placeholder="例: 5.0 (5%)"> <option value="労災保険">労災保険</option>
</div> </select>
<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> <div class="form-group">
<label>従業員負担率 (%) *</label>
<!-- 所得税率表タブ --> <input
<div id="tab-tax" class="tab-content"> type="number"
<h2>所得税率表</h2> name="employee_rate"
<div class="form-group" style="max-width:300px;"> step="0.0001"
<label>扶養人数でフィルター</label> required
<select id="filterDependents" onchange="loadIncomeTax()"> placeholder="例: 5.0 (5%)"
<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> </div>
<button class="btn btn-primary" onclick="document.getElementById('csvFile').click()">CSVインポート</button> <div class="form-group">
<input type="file" id="csvFile" accept=".csv" style="display:none;" onchange="importTaxTable(event)"> <label>会社負担率 (%) *</label>
<div id="taxList"></div> <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>
<!-- 所得税率表タブ -->
<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> </div>
<script> <script>
const API_BASE = 'http://localhost:8000'; const API_BASE = "";
function showTab(tabName) { function showTab(tabName) {
document.querySelectorAll('.nav-tab').forEach(tab => tab.classList.remove('active')); document
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); .querySelectorAll(".nav-tab")
.forEach((tab) => tab.classList.remove("active"));
document
.querySelectorAll(".tab-content")
.forEach((content) => content.classList.remove("active"));
if (tabName === 'insurance') { if (tabName === "insurance") {
document.querySelectorAll('.nav-tab')[0].classList.add('active'); document.querySelectorAll(".nav-tab")[0].classList.add("active");
document.getElementById('tab-insurance').classList.add('active'); document.getElementById("tab-insurance").classList.add("active");
loadInsuranceRates(); loadInsuranceRates();
} else if (tabName === 'tax') { } else if (tabName === "tax") {
document.querySelectorAll('.nav-tab')[1].classList.add('active'); document.querySelectorAll(".nav-tab")[1].classList.add("active");
document.getElementById('tab-tax').classList.add('active'); document.getElementById("tab-tax").classList.add("active");
loadIncomeTax(); loadIncomeTax();
}
} }
}
function showInsuranceForm() { function showInsuranceForm() {
document.getElementById('insuranceForm').style.display = 'block'; document.getElementById("insuranceForm").style.display = "block";
} }
function hideInsuranceForm() { function hideInsuranceForm() {
document.getElementById('insuranceForm').style.display = 'none'; document.getElementById("insuranceForm").style.display = "none";
document.querySelector('#insuranceForm form').reset(); document.querySelector("#insuranceForm form").reset();
} }
async function loadInsuranceRates() { async function loadInsuranceRates() {
try { try {
const response = await fetch(`${API_BASE}/payroll/settings/insurance-rates`); const response = await fetch(
const rates = await response.json(); `${API_BASE}/payroll/settings/insurance-rates`,
);
const rates = await response.json();
const html = ` const html = `
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -192,70 +242,82 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${rates.map(rate => ` ${rates
.map(
(rate) => `
<tr> <tr>
<td>${rate.rate_type}</td> <td>${rate.rate_type}</td>
<td>${(parseFloat(rate.employee_rate) * 100).toFixed(2)}%</td> <td>${(parseFloat(rate.employee_rate) * 100).toFixed(2)}%</td>
<td>${(parseFloat(rate.employer_rate) * 100).toFixed(2)}%</td> <td>${(parseFloat(rate.employer_rate) * 100).toFixed(2)}%</td>
<td>${rate.valid_from} ${rate.valid_to || '現在'}</td> <td>${rate.valid_from} ${rate.valid_to || "現在"}</td>
<td>${rate.notes || '-'}</td> <td>${rate.notes || "-"}</td>
</tr> </tr>
`).join('')} `,
)
.join("")}
</tbody> </tbody>
</table> </table>
`; `;
document.getElementById('insuranceList').innerHTML = html; document.getElementById("insuranceList").innerHTML = html;
} catch (error) { } catch (error) {
alert('保険料率の取得に失敗しました'); alert("保険料率の取得に失敗しました");
console.error(error); console.error(error);
}
} }
}
async function saveInsuranceRate(event) { async function saveInsuranceRate(event) {
event.preventDefault(); event.preventDefault();
const form = event.target; const form = event.target;
const formData = new FormData(form); const formData = new FormData(form);
const data = Object.fromEntries(formData.entries()); const data = Object.fromEntries(formData.entries());
// パーセントを小数に変換 // パーセントを小数に変換
data.employee_rate = parseFloat(data.employee_rate) / 100; data.employee_rate = parseFloat(data.employee_rate) / 100;
data.employer_rate = parseFloat(data.employer_rate) / 100; data.employer_rate = parseFloat(data.employer_rate) / 100;
// 空の値を除外 // 空の値を除外
if (!data.valid_to) delete data.valid_to; if (!data.valid_to) delete data.valid_to;
if (!data.notes) delete data.notes; if (!data.notes) delete data.notes;
try { try {
const response = await fetch(`${API_BASE}/payroll/settings/insurance-rates`, { const response = await fetch(
method: 'POST', `${API_BASE}/payroll/settings/insurance-rates`,
headers: {'Content-Type': 'application/json'}, {
body: JSON.stringify(data) method: "POST",
}); headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
},
);
if (response.ok) { if (response.ok) {
alert('保険料率を登録しました'); alert("保険料率を登録しました");
hideInsuranceForm(); hideInsuranceForm();
loadInsuranceRates(); loadInsuranceRates();
} else { } else {
const error = await response.json(); const error = await response.json();
alert('登録に失敗しました: ' + error.detail); alert("登録に失敗しました: " + error.detail);
} }
} catch (error) { } catch (error) {
alert('登録に失敗しました'); alert("登録に失敗しました");
console.error(error); console.error(error);
}
} }
}
async function loadIncomeTax() { async function loadIncomeTax() {
const dependentsFilter = document.getElementById('filterDependents').value; const dependentsFilter =
const params = dependentsFilter ? `?dependents_count=${dependentsFilter}` : ''; document.getElementById("filterDependents").value;
const params = dependentsFilter
? `?dependents_count=${dependentsFilter}`
: "";
try { try {
const response = await fetch(`${API_BASE}/payroll/settings/income-tax${params}`); const response = await fetch(
const taxes = await response.json(); `${API_BASE}/payroll/settings/income-tax${params}`,
);
const taxes = await response.json();
const html = ` const html = `
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -267,60 +329,70 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${taxes.map(tax => ` ${taxes
.map(
(tax) => `
<tr> <tr>
<td>${tax.dependents_count}人</td> <td>${tax.dependents_count}人</td>
<td>¥${Number(tax.monthly_income_from).toLocaleString()}</td> <td>¥${Number(tax.monthly_income_from).toLocaleString()}</td>
<td>¥${Number(tax.monthly_income_to).toLocaleString()}</td> <td>¥${Number(tax.monthly_income_to).toLocaleString()}</td>
<td>¥${Number(tax.tax_amount).toLocaleString()}</td> <td>¥${Number(tax.tax_amount).toLocaleString()}</td>
<td>${tax.valid_from} ${tax.valid_to || '現在'}</td> <td>${tax.valid_from} ${tax.valid_to || "現在"}</td>
</tr> </tr>
`).join('')} `,
)
.join("")}
</tbody> </tbody>
</table> </table>
`; `;
document.getElementById('taxList').innerHTML = html; document.getElementById("taxList").innerHTML = html;
} catch (error) { } catch (error) {
alert('所得税率表の取得に失敗しました'); alert("所得税率表の取得に失敗しました");
console.error(error); 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);
} }
async function importTaxTable(event) { event.target.value = "";
const file = event.target.files[0]; }
if (!file) return;
const formData = new FormData(); // 初期化
formData.append('file', file); loadInsuranceRates();
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> </script>
</body> </body>
</html> </html>

View File

@@ -1,99 +1,135 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>給与管理システム</title> <title>給与管理システム</title>
<link rel="stylesheet" href="/css/style.css"> <link rel="stylesheet" href="/css/style.css" />
<style> <style>
.menu-grid { .menu-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px; gap: 20px;
margin-top: 30px; margin-top: 30px;
} }
.menu-card { .menu-card {
padding: 30px; padding: 30px;
border: 2px solid #007bff; border: 2px solid #007bff;
border-radius: 10px; border-radius: 10px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
} }
.menu-card:hover { .menu-card:hover {
background: #007bff; background: #007bff;
color: white; color: white;
transform: translateY(-5px); transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,123,255,0.3); box-shadow: 0 5px 15px rgba(0, 123, 255, 0.3);
} }
.menu-card h2 { .menu-card h2 {
margin: 0 0 15px 0; margin: 0 0 15px 0;
font-size: 1.5em; font-size: 1.5em;
} }
.menu-card p { .menu-card p {
margin: 0; margin: 0;
opacity: 0.8; opacity: 0.8;
} }
.menu-card:hover p { .menu-card:hover p {
opacity: 1; opacity: 1;
} }
.icon { .icon {
font-size: 3em; font-size: 3em;
margin-bottom: 15px; margin-bottom: 15px;
} }
.back-link { .back-link {
display: inline-block; display: none;
margin-bottom: 20px; }
color: #007bff;
text-decoration: none;
}
.back-link:hover {
text-decoration: underline;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<a href="../index.html" class="back-link">← メインメニューに戻る</a> <button
onclick="location.href = '../index.html'"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-bottom: 20px;
"
>
← メインメニューに戻る
</button>
<h1>給与管理システム</h1> <h1>給与管理システム</h1>
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p> <p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
<div class="menu-grid"> <div class="menu-grid">
<div class="menu-card" onclick="location.href='payroll-employees.html'"> <div
<div class="icon">👥</div> class="menu-card"
<h2>従業員管理</h2> onclick="location.href = 'payroll-employees.html'"
<p>従業員の基本情報、扶養家族情報を管理します</p> >
</div> <div class="icon">👥</div>
<h2>従業員管理</h2>
<div class="menu-card" onclick="location.href='payroll-salary-settings.html'"> <p>従業員の基本情報、扶養家族情報を管理します</p>
<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>
<div style="margin-top: 50px; padding: 20px; background: #f0f8ff; border-radius: 5px;"> <div
<h3>📋 給与システムの使い方</h3> class="menu-card"
<ol> onclick="location.href = 'payroll-salary-settings.html'"
<li><strong>従業員管理</strong>で従業員を登録し、扶養家族情報を入力します</li> >
<li><strong>給与設定</strong>で各従業員の基本給や手当を設定します</li> <div class="icon">💰</div>
<li><strong>税率・保険料設定</strong>で社会保険料率や所得税率表を確認・更新します</li> <h2>給与設定</h2>
<li><strong>月次給与計算</strong>で勤怠情報を入力し、給与を計算・承認します</li> <p>各従業員の給与設定、基本給、手当などを管理します</p>
</ol>
</div> </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> </div>
</body> </body>
</html> </html>

View File

@@ -12,26 +12,9 @@ server {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
# Proxy API requests to backend container # Proxy all API requests to backend
location /payroll/ { location ~ ^/(accounts|journals|ledger|trial-balance|opening-balances|general-ledger|payroll|users|month-locks|year-locks|cash|file-upload) {
proxy_pass http://backend:18080/payroll/; proxy_pass http://backend:18080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /payroll/employees/ {
proxy_pass http://backend:18080/payroll/employees/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Generic fallback to backend for other API paths
location /api/ {
proxy_pass http://backend:18080/;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@@ -81,7 +81,7 @@
<div id="result"></div> <div id="result"></div>
<script> <script>
const API = "http://127.0.0.1:18080"; const API = "";
let accounts = []; let accounts = [];
// ------------------------------------ // ------------------------------------

View File

@@ -115,6 +115,22 @@
</style> </style>
</head> </head>
<body> <body>
<button
onclick="location.href = 'index.html'"
class="back-button"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 首ページに戻る
</button>
<h2>仕訳入力</h2> <h2>仕訳入力</h2>
<div class="row"> <div class="row">
@@ -295,9 +311,7 @@
<div id="printArea"></div> <div id="printArea"></div>
<script> <script>
let rowSeq = 0; // 各行唯一ID const API = "";
const API = "http://127.0.0.1:18080";
// 千分位カンマフォーマット関数 // 千分位カンマフォーマット関数
function formatNumberInput(input) { function formatNumberInput(input) {
@@ -909,7 +923,7 @@
} }
} }
const API_BASE = "http://127.0.0.1:18080"; const API_BASE = "";
async function callApi(path) { async function callApi(path) {
try { try {

View File

@@ -50,7 +50,7 @@
</table> </table>
<script> <script>
const API = "http://127.0.0.1:18080"; const API = "";
async function search() { async function search() {
const from = document.getElementById("fromDate").value; const from = document.getElementById("fromDate").value;

View File

@@ -54,12 +54,25 @@
</table> </table>
<div style="margin-top: 16px"> <div style="margin-top: 16px">
<button onclick="window.close()">閉じる</button> <button
<button onclick="goBack()">一覧に戻る</button> onclick="goBack()"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 仕訳一覧に戻る
</button>
</div> </div>
<script> <script>
const API = "http://127.0.0.1:18080"; const API = "";
async function loadJournalEntry() { async function loadJournalEntry() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);

View File

@@ -40,7 +40,22 @@
<br /><br /> <br /><br />
<button onclick="submitJournal()">保存</button> <button onclick="submitJournal()">保存</button>
<a href="index.html">← 戻る</a> <button
onclick="location.href = 'index.html'"
class="back-button"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 首ページに戻る
</button>
<script src="js/api.js"></script> <script src="js/api.js"></script>
<script src="js/journal.js"></script> <script src="js/journal.js"></script>

View File

@@ -1,4 +1,4 @@
const API_BASE = "http://127.0.0.1:18080"; const API_BASE = "";
async function apiGet(path, params = {}) { async function apiGet(path, params = {}) {
const query = new URLSearchParams(params).toString(); const query = new URLSearchParams(params).toString();

View File

@@ -42,7 +42,7 @@ async function submitJournal() {
} }
try { try {
await fetch("http://127.0.0.1:18080/journals", { await fetch(`/journals`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({

View File

@@ -127,7 +127,7 @@ function recalcTotals() {
document.getElementById("totalDebit").innerText = formatNumber(finalDebit); document.getElementById("totalDebit").innerText = formatNumber(finalDebit);
document.getElementById("totalCredit").innerText = formatNumber(finalCredit); document.getElementById("totalCredit").innerText = formatNumber(finalCredit);
document.getElementById("diff").innerText = formatNumber( document.getElementById("diff").innerText = formatNumber(
finalDebit - finalCredit finalDebit - finalCredit,
); );
// 小計行を更新 // 小計行を更新
@@ -144,7 +144,7 @@ function updateSubtotalRows() {
debit: sum.debit + Number(acc.opening_debit || 0), debit: sum.debit + Number(acc.opening_debit || 0),
credit: sum.credit + Number(acc.opening_credit || 0), credit: sum.credit + Number(acc.opening_credit || 0),
}), }),
{ debit: 0, credit: 0 } { debit: 0, credit: 0 },
); );
// 対応する小計行を探して更新 // 対応する小計行を探して更新
@@ -167,7 +167,7 @@ async function loadOpeningBalances() {
accounts = data; accounts = data;
retainedEarnings = accounts.find( retainedEarnings = accounts.find(
(a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益" (a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益",
); );
const tbody = document.querySelector("#balanceTable tbody"); const tbody = document.querySelector("#balanceTable tbody");
@@ -224,7 +224,7 @@ async function loadOpeningBalances() {
debit: sum.debit + Number(acc.opening_debit || 0), debit: sum.debit + Number(acc.opening_debit || 0),
credit: sum.credit + Number(acc.opening_credit || 0), credit: sum.credit + Number(acc.opening_credit || 0),
}), }),
{ debit: 0, credit: 0 } { debit: 0, credit: 0 },
); );
const totalTr = document.createElement("tr"); const totalTr = document.createElement("tr");
@@ -243,14 +243,12 @@ async function loadOpeningBalances() {
}); });
// 年度锁定チェック // 年度锁定チェック
fetch( fetch(`/opening-balances/lock-status?fiscal_year=${year}`)
`http://127.0.0.1:18080/opening-balances/lock-status?fiscal_year=${year}`
)
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {
isLocked = data.is_locked; isLocked = data.is_locked;
document.querySelector( document.querySelector(
"button[onclick='saveOpeningBalances()']" "button[onclick='saveOpeningBalances()']",
).disabled = isLocked; ).disabled = isLocked;
if (isLocked) { if (isLocked) {
alert("この会計年度の期首残高は確定されています。"); alert("この会計年度の期首残高は確定されています。");
@@ -273,7 +271,7 @@ async function saveOpeningBalances() {
(a) => (a) =>
a.account_name !== "繰越利益剰余金" && a.account_name !== "繰越利益剰余金" &&
a.account_name !== "繰越利益" && a.account_name !== "繰越利益" &&
(Number(a.opening_debit) !== 0 || Number(a.opening_credit) !== 0) (Number(a.opening_debit) !== 0 || Number(a.opening_credit) !== 0),
) )
.map((a) => ({ .map((a) => ({
account_id: a.account_id, account_id: a.account_id,
@@ -282,7 +280,7 @@ async function saveOpeningBalances() {
})); }));
try { try {
await fetch("http://127.0.0.1:18080/opening-balances", { await fetch(`/opening-balances`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({

View File

@@ -1,7 +1,13 @@
document.addEventListener("DOMContentLoaded", () => { // ===============================================
console.log("trial-balance.js 已执行"); // trial-balance.js - 試算表データ読み込みスクリプト
// ===============================================
console.log("🔧 trial-balance.js ファイルが読み込まれました");
const API_URL = "http://127.0.0.1:18080/trial-balance"; // 初期读入関数
function initTrialBalance() {
console.log("✓ initTrialBalance() 関数が実行されました");
const API_URL = `/trial-balance`;
// デフォルト期間を計算5/31決算6月開始現在月末 // デフォルト期間を計算5/31決算6月開始現在月末
function getDefaultPeriod() { function getDefaultPeriod() {
@@ -28,7 +34,7 @@ document.addEventListener("DOMContentLoaded", () => {
const lastDayOfMonth = new Date(fiscalEndYear, currentMonth, 0).getDate(); const lastDayOfMonth = new Date(fiscalEndYear, currentMonth, 0).getDate();
const dateTo = `${fiscalEndYear}-${String(currentMonth).padStart( const dateTo = `${fiscalEndYear}-${String(currentMonth).padStart(
2, 2,
"0" "0",
)}-${String(lastDayOfMonth).padStart(2, "0")}`; )}-${String(lastDayOfMonth).padStart(2, "0")}`;
return { dateFrom, dateTo }; return { dateFrom, dateTo };
@@ -39,8 +45,13 @@ document.addEventListener("DOMContentLoaded", () => {
const url = `${API_URL}?date_from=${dateFrom}&date_to=${dateTo}`; const url = `${API_URL}?date_from=${dateFrom}&date_to=${dateTo}`;
console.log("読み込み URL:", url); console.log("読み込み URL:", url);
// 加載中のステータス表示
const loadingEl = document.getElementById("loadingStatus");
if (loadingEl) loadingEl.style.display = "inline";
fetch(url) fetch(url)
.then((r) => { .then((r) => {
console.log("API Response Status:", r.status);
if (!r.ok) throw new Error("API 返回错误: " + r.status); if (!r.ok) throw new Error("API 返回错误: " + r.status);
return r.json(); return r.json();
}) })
@@ -116,10 +127,23 @@ document.addEventListener("DOMContentLoaded", () => {
totalClosing.toLocaleString(); totalClosing.toLocaleString();
document.getElementById("totalRatio").textContent = document.getElementById("totalRatio").textContent =
data.totals?.ratio ?? "100.00"; data.totals?.ratio ?? "100.00";
// 加載中ステータスを非表示
const loadingEl = document.getElementById("loadingStatus");
if (loadingEl) loadingEl.style.display = "none";
}) })
.catch((err) => { .catch((err) => {
console.error("試算表読取失敗:", err); console.error("試算表読取失敗:", err);
alert("試算表読取失敗、コンソールログを確認してください"); const tbody = document.querySelector("#trialBalanceTable tbody");
if (tbody) {
tbody.innerHTML = `<tr><td colspan="7" style="text-align:center; color:red;">エラー: ${err.message}</td></tr>`;
}
// 加載中ステータスを非表示
const loadingEl = document.getElementById("loadingStatus");
if (loadingEl) loadingEl.style.display = "none";
alert(
`試算表読取失敗: ${err.message}\n\nコンソールログ(F12)を確認してください`,
);
}); });
} }
@@ -170,9 +194,23 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
// 初期読み込み5/31決算に対応 // 初期読み込み5/31決算に対応
const defaultPeriod = getDefaultPeriod(); try {
document.getElementById("dateFrom").value = defaultPeriod.dateFrom; const defaultPeriod = getDefaultPeriod();
document.getElementById("dateTo").value = defaultPeriod.dateTo; document.getElementById("dateFrom").value = defaultPeriod.dateFrom;
document.getElementById("dateTo").value = defaultPeriod.dateTo;
loadTrialBalance(defaultPeriod.dateFrom, defaultPeriod.dateTo);
console.log("✓ 初期読み込み完了");
} catch (err) {
console.error("初期読み込みエラー:", err);
}
}
loadTrialBalance(defaultPeriod.dateFrom, defaultPeriod.dateTo); // 複数の方式で初期化を実行iOS互換性を向上
}); document.addEventListener("DOMContentLoaded", initTrialBalance);
window.addEventListener("load", initTrialBalance);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initTrialBalance);
} else {
// ページがすでに読み込まれている場合、すぐに実行
setTimeout(initTrialBalance, 100);
}

View File

@@ -22,7 +22,22 @@
<tbody id="ledger-body"></tbody> <tbody id="ledger-body"></tbody>
</table> </table>
<a href="index.html">← 試算表へ戻る</a> <button
onclick="location.href = 'trial-balance.html'"
class="back-button"
style="
margin: 20px 0 0 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 試算表に戻る
</button>
<script src="js/api.js"></script> <script src="js/api.js"></script>
<script src="js/ledger.js"></script> <script src="js/ledger.js"></script>

View File

@@ -40,7 +40,22 @@
<br /> <br />
<button onclick="saveOpeningBalances()">保存</button> <button onclick="saveOpeningBalances()">保存</button>
<button onclick="location.href = 'trial-balance.html'">← 戻る</button> <button
onclick="location.href = 'trial-balance.html'"
class="back-button"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 試算表に戻る
</button>
<script src="js/api.js"></script> <script src="js/api.js"></script>
<script src="js/opening_balance.js"></script> <script src="js/opening_balance.js"></script>

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -100,7 +100,7 @@
</table> </table>
<script> <script>
const API = "http://127.0.0.1:8000"; const API = "";
// -------------------- // --------------------
// 初期化 // 初期化
@@ -140,7 +140,7 @@
}); });
document.getElementById("totalBalance").innerText = Number( document.getElementById("totalBalance").innerText = Number(
data.total_balance data.total_balance,
).toLocaleString(); ).toLocaleString();
} }
@@ -154,7 +154,7 @@
const sel = document.getElementById("accountSelect"); const sel = document.getElementById("accountSelect");
sel.innerHTML = data.items sel.innerHTML = data.items
.map( .map(
(i) => `<option value="${i.account_id}">${i.account_name}</option>` (i) => `<option value="${i.account_id}">${i.account_name}</option>`,
) )
.join(""); .join("");

View File

@@ -160,7 +160,22 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a> <button
onclick="location.href = 'payroll.html'"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-bottom: 20px;
"
>
← 給与管理メニューに戻る
</button>
<h1>給与管理 - 給与・賞与計算</h1> <h1>給与管理 - 給与・賞与計算</h1>

View File

@@ -229,7 +229,22 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a> <button
onclick="location.href = 'payroll.html'"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-bottom: 20px;
"
>
← 給与管理メニューに戻る
</button>
<h1>給与管理 - 従業員管理</h1> <h1>給与管理 - 従業員管理</h1>

View File

@@ -71,7 +71,22 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a> <button
onclick="location.href = 'payroll.html'"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-bottom: 20px;
"
>
← 給与管理メニューに戻る
</button>
<h1>給与管理 - 給与設定</h1> <h1>給与管理 - 給与設定</h1>

View File

@@ -171,7 +171,22 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<a href="payroll.html" class="back-link">← 給与管理メニューに戻る</a> <button
onclick="location.href = 'payroll.html'"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-bottom: 20px;
"
>
← 給与管理メニューに戻る
</button>
<h1>給与管理 - 設定</h1> <h1>給与管理 - 設定</h1>

View File

@@ -55,7 +55,21 @@
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<a href="../index.html" class="back-link">← メインメニューに戻る</a> <button
onclick="location.href = '../index.html'"
class="back-button"
style="
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← メインメニューに戻る
</button>
<h1>給与管理システム</h1> <h1>給与管理システム</h1>
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p> <p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="ja"> <html lang="ja">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -6,7 +6,7 @@
<title>試算表</title> <title>試算表</title>
<style> <style>
body { body {
font-family: 'MS Gothic', 'Meiryo', sans-serif; font-family: "MS Gothic", "Meiryo", sans-serif;
margin: 24px auto; margin: 24px auto;
font-size: 15px; font-size: 15px;
max-width: 1400px; max-width: 1400px;
@@ -39,7 +39,7 @@
.search-form button { .search-form button {
padding: 6px 20px; padding: 6px 20px;
margin: 0 5px; margin: 0 5px;
background: #4CAF50; background: #4caf50;
color: white; color: white;
border: none; border: none;
border-radius: 3px; border-radius: 3px;
@@ -50,7 +50,7 @@
background: #45a049; background: #45a049;
} }
.search-form button.secondary { .search-form button.secondary {
background: #2196F3; background: #2196f3;
} }
.search-form button.secondary:hover { .search-form button.secondary:hover {
background: #0b7dda; background: #0b7dda;
@@ -96,8 +96,31 @@
</style> </style>
</head> </head>
<body> <body>
<button
onclick="location.href = 'index.html'"
class="back-button"
style="
margin: 0 0 20px 0;
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
"
>
← 首ページに戻る
</button>
<h2>残高試算表(貸借・損益)</h2> <h2>残高試算表(貸借・損益)</h2>
<div
id="loadingStatus"
style="display: none; color: #666; font-size: 12px; margin: 10px 0"
>
読み込み中...
</div>
<div class="search-form"> <div class="search-form">
<label for="dateFrom">開始年月日:</label> <label for="dateFrom">開始年月日:</label>
<input type="date" id="dateFrom" min="1900-01-01" max="2099-12-31" /> <input type="date" id="dateFrom" min="1900-01-01" max="2099-12-31" />
@@ -111,7 +134,7 @@
<div class="period-info"> <div class="period-info">
<span id="periodInfo">令和7年 1月 1日 令和7年 12月31日</span> <span id="periodInfo">令和7年 1月 1日 令和7年 12月31日</span>
<span style="margin-left: 20px;">単位: 円</span> <span style="margin-left: 20px">単位: 円</span>
</div> </div>
<table id="trialBalanceTable"> <table id="trialBalanceTable">
@@ -142,8 +165,29 @@
</table> </table>
<script src="js/trial-balance.js"></script> <script src="js/trial-balance.js"></script>
</body>
</html> <!-- 立即初始化脚本iOS Safari 的外部脚本加载问题的备选方案 -->
<script>
// 确保脚本被加载和执行
console.log("trial-balance.html inline script 执行中...");
if (typeof initTrialBalance === "function") {
console.log("initTrialBalance 函数が見つかりました, 実行します");
initTrialBalance();
} else {
console.warn(
"initTrialBalance 関数が見つかりません。js/trial-balance.js が読み込まれていない可能性があります",
);
// タイムアウトで再度チェック
setTimeout(() => {
if (typeof initTrialBalance === "function") {
console.log("遅延実行: initTrialBalance を実行します");
initTrialBalance();
} else {
console.error("js/trial-balance.js が読み込まれていません!");
}
}, 500);
}
</script> </script>
</body> </body>
</html> </html>

View File

@@ -266,7 +266,7 @@
</div> </div>
<script> <script>
const API_BASE = "http://localhost:8000"; const API_BASE = "";
function addTestResult(message, status = "info") { function addTestResult(message, status = "info") {
const div = document.getElementById("testResults"); const div = document.getElementById("testResults");