修正
This commit is contained in:
79
DIAGNOSIS.md
Normal file
79
DIAGNOSIS.md
Normal 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是否在同一网络中?
|
||||
@@ -11,8 +11,6 @@ def get_trial_balance(
|
||||
date_from: 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:
|
||||
fiscal_year = 2025
|
||||
@@ -25,5 +23,4 @@ def get_trial_balance(
|
||||
raise HTTPException(status_code=400, detail="期間指定が不正です。")
|
||||
|
||||
result = fetch_trial_balance(date_from, date_to)
|
||||
|
||||
return result
|
||||
|
||||
@@ -41,6 +41,7 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
||||
""")
|
||||
accounts = cur.fetchall()
|
||||
|
||||
|
||||
# 科目データを格納
|
||||
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_closing = sum(data["closing_balance"] for data in account_data.values())
|
||||
|
||||
return {
|
||||
result = {
|
||||
"totals": {
|
||||
"opening_balance": str(total_opening),
|
||||
"debit": str(total_debit),
|
||||
@@ -306,3 +307,5 @@ def fetch_trial_balance(date_from: str, date_to: str):
|
||||
"accounts": result_accounts,
|
||||
"grand_total_closing": str(grand_total_closing)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -28,7 +28,7 @@
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API_BASE = "http://127.0.0.1:8080";
|
||||
const API_BASE = "";
|
||||
|
||||
function formatYen(v) {
|
||||
return "¥ " + Number(v).toLocaleString("ja-JP");
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>給与管理 - 月次給与計算</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<style>
|
||||
.filters {
|
||||
margin-bottom: 20px;
|
||||
@@ -54,7 +54,8 @@
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-group input, .form-group select {
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -116,19 +117,51 @@
|
||||
<div class="filters">
|
||||
<label>
|
||||
対象年月:
|
||||
<input type="number" id="filterYear" style="width: 100px;" placeholder="年">
|
||||
<input
|
||||
type="number"
|
||||
id="filterYear"
|
||||
style="width: 100px"
|
||||
placeholder="年"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<input type="number" id="filterMonth" style="width: 80px;" min="1" max="12" placeholder="月">
|
||||
<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>
|
||||
<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;">
|
||||
<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">
|
||||
@@ -141,15 +174,21 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>対象年 *</label>
|
||||
<input type="number" name="payroll_year" required>
|
||||
<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>
|
||||
<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>
|
||||
<input type="date" name="payment_date" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,29 +197,44 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<label>勤務時間</label>
|
||||
<input type="number" name="working_hours" step="0.5" value="0">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<input type="number" name="absent_days" step="0.5" value="0" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,53 +244,90 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>その他手当</label>
|
||||
<input type="number" name="other_allowance" step="0.01" value="0">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
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>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
const API_BASE = "";
|
||||
|
||||
async function loadEmployees() {
|
||||
const response = await fetch(`${API_BASE}/payroll/employees/?is_active=true`);
|
||||
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('');
|
||||
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 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);
|
||||
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 response = await fetch(
|
||||
`${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>
|
||||
<strong>${p.payroll_year}年${p.payroll_month}月</strong>
|
||||
@@ -250,38 +341,43 @@
|
||||
(総支給: ¥${Number(p.total_payment).toLocaleString()} - 控除: ¥${Number(p.total_deduction).toLocaleString()})
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
document.getElementById('payrollList').innerHTML = html || '<p>給与データがありません</p>';
|
||||
document.getElementById("payrollList").innerHTML =
|
||||
html || "<p>給与データがありません</p>";
|
||||
} catch (error) {
|
||||
alert('給与一覧の取得に失敗しました');
|
||||
alert("給与一覧の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusLabel(status) {
|
||||
const labels = {
|
||||
'draft': '下書き',
|
||||
'calculated': '計算済み',
|
||||
'approved': '承認済み',
|
||||
'paid': '支払済み'
|
||||
draft: "下書き",
|
||||
calculated: "計算済み",
|
||||
approved: "承認済み",
|
||||
paid: "支払済み",
|
||||
};
|
||||
return labels[status] || status;
|
||||
}
|
||||
|
||||
function showCalculateForm() {
|
||||
document.getElementById('calculateModal').style.display = 'block';
|
||||
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;
|
||||
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();
|
||||
document.getElementById("calculateModal").style.display = "none";
|
||||
document.getElementById("calculateForm").reset();
|
||||
}
|
||||
|
||||
async function calculatePayroll(event) {
|
||||
@@ -291,34 +387,39 @@
|
||||
const data = {};
|
||||
|
||||
formData.forEach((value, key) => {
|
||||
data[key] = isNaN(value) || value === '' ? value : Number(value);
|
||||
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)
|
||||
});
|
||||
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('給与計算が完了しました');
|
||||
alert("給与計算が完了しました");
|
||||
closeCalculateForm();
|
||||
viewPayroll(result.payroll_id);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('計算に失敗しました: ' + error.detail);
|
||||
alert("計算に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('計算に失敗しました');
|
||||
alert("計算に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function viewPayroll(payrollId) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}`);
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/calculation/${payrollId}`,
|
||||
);
|
||||
const payroll = await response.json();
|
||||
|
||||
const html = `
|
||||
@@ -363,71 +464,83 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${payroll.status === 'calculated' ? `
|
||||
${
|
||||
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' });
|
||||
document.getElementById("payrollDetail").innerHTML = html;
|
||||
document.getElementById("payrollDetail").style.display = "block";
|
||||
document
|
||||
.getElementById("payrollDetail")
|
||||
.scrollIntoView({ behavior: "smooth" });
|
||||
} catch (error) {
|
||||
alert('給与明細の取得に失敗しました');
|
||||
alert("給与明細の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function approvePayroll(payrollId) {
|
||||
if (!confirm('この給与を承認しますか?')) return;
|
||||
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' })
|
||||
});
|
||||
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('承認しました');
|
||||
alert("承認しました");
|
||||
viewPayroll(payrollId);
|
||||
loadPayrolls();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('承認に失敗しました: ' + error.detail);
|
||||
alert("承認に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('承認に失敗しました');
|
||||
alert("承認に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function recalculatePayroll(payrollId) {
|
||||
if (!confirm('給与を再計算しますか?')) return;
|
||||
if (!confirm("給与を再計算しますか?")) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/calculation/${payrollId}/recalculate`, {
|
||||
method: 'POST'
|
||||
});
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/calculation/${payrollId}/recalculate`,
|
||||
{
|
||||
method: "POST",
|
||||
},
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
alert('再計算が完了しました');
|
||||
alert("再計算が完了しました");
|
||||
viewPayroll(payrollId);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('再計算に失敗しました: ' + error.detail);
|
||||
alert("再計算に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('再計算に失敗しました');
|
||||
alert("再計算に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初期化
|
||||
const now = new Date();
|
||||
document.getElementById('filterYear').value = now.getFullYear();
|
||||
document.getElementById('filterMonth').value = now.getMonth() + 1;
|
||||
document.getElementById("filterYear").value = now.getFullYear();
|
||||
document.getElementById("filterMonth").value = now.getMonth() + 1;
|
||||
loadPayrolls();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>給与管理 - 従業員管理</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<style>
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
@@ -50,7 +50,9 @@
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-group input, .form-group select, .form-group textarea {
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -95,16 +97,30 @@
|
||||
<h1>給与管理 - 従業員管理</h1>
|
||||
|
||||
<div class="nav-tabs">
|
||||
<button class="nav-tab active" onclick="showTab('list')">従業員一覧</button>
|
||||
<button class="nav-tab active" onclick="showTab('list')">
|
||||
従業員一覧
|
||||
</button>
|
||||
<button class="nav-tab" onclick="showTab('create')">新規登録</button>
|
||||
<button class="nav-tab" onclick="showTab('detail')" id="detailTab" style="display:none;">従業員詳細</button>
|
||||
<button
|
||||
class="nav-tab"
|
||||
onclick="showTab('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()">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="filterActive"
|
||||
checked
|
||||
onchange="loadEmployees()"
|
||||
/>
|
||||
在職中のみ表示
|
||||
</label>
|
||||
</div>
|
||||
@@ -117,39 +133,39 @@
|
||||
<form id="createForm" onsubmit="createEmployee(event)">
|
||||
<div class="form-group">
|
||||
<label>従業員コード *</label>
|
||||
<input type="text" name="employee_code" required>
|
||||
<input type="text" name="employee_code" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>氏名 *</label>
|
||||
<input type="text" name="name" required>
|
||||
<input type="text" name="name" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>フリガナ</label>
|
||||
<input type="text" name="name_kana">
|
||||
<input type="text" name="name_kana" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>メールアドレス</label>
|
||||
<input type="email" name="email">
|
||||
<input type="email" name="email" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>電話番号</label>
|
||||
<input type="tel" name="phone">
|
||||
<input type="tel" name="phone" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>入社日 *</label>
|
||||
<input type="date" name="hire_date" required>
|
||||
<input type="date" name="hire_date" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>銀行名</label>
|
||||
<input type="text" name="bank_name">
|
||||
<input type="text" name="bank_name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>支店名</label>
|
||||
<input type="text" name="bank_branch">
|
||||
<input type="text" name="bank_branch" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>口座番号</label>
|
||||
<input type="text" name="bank_account_number">
|
||||
<input type="text" name="bank_account_number" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>口座種別</label>
|
||||
@@ -164,7 +180,13 @@
|
||||
<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>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onclick="showTab('list')"
|
||||
>
|
||||
キャンセル
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -175,46 +197,55 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
const API_BASE = "";
|
||||
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'));
|
||||
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');
|
||||
if (tabName === "list") {
|
||||
document.querySelector(".nav-tab").classList.add("active");
|
||||
document.getElementById("tab-list").classList.add("active");
|
||||
loadEmployees();
|
||||
} else if (tabName === 'create') {
|
||||
document.querySelectorAll('.nav-tab')[1].classList.add('active');
|
||||
document.getElementById('tab-create').classList.add('active');
|
||||
} else if (tabName === 'detail') {
|
||||
document.getElementById('detailTab').classList.add('active');
|
||||
document.getElementById('tab-detail').classList.add('active');
|
||||
} else if (tabName === "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' : ''}`;
|
||||
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 => `
|
||||
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})` : ''}
|
||||
${emp.name_kana ? `(${emp.name_kana})` : ""}
|
||||
<br>
|
||||
<small>入社日: ${emp.hire_date} ${emp.termination_date ? ` | 退職日: ${emp.termination_date}` : ''}</small>
|
||||
<small>入社日: ${emp.hire_date} ${emp.termination_date ? ` | 退職日: ${emp.termination_date}` : ""}</small>
|
||||
</div>
|
||||
`).join('');
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
document.getElementById('employeeList').innerHTML = html || '<p>従業員が登録されていません</p>';
|
||||
document.getElementById("employeeList").innerHTML =
|
||||
html || "<p>従業員が登録されていません</p>";
|
||||
} catch (error) {
|
||||
alert('従業員一覧の取得に失敗しました');
|
||||
alert("従業員一覧の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@@ -226,71 +257,79 @@
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
// 空の値を除外
|
||||
Object.keys(data).forEach(key => {
|
||||
if (data[key] === '') {
|
||||
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)
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('従業員を登録しました');
|
||||
alert("従業員を登録しました");
|
||||
form.reset();
|
||||
showTab('list');
|
||||
showTab("list");
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('登録に失敗しました: ' + error.detail);
|
||||
alert("登録に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('登録に失敗しました');
|
||||
alert("登録に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function viewEmployee(employeeId) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/employees/${employeeId}`);
|
||||
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.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>
|
||||
<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 => `
|
||||
${
|
||||
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>
|
||||
${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>'}
|
||||
`,
|
||||
)
|
||||
.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');
|
||||
document.getElementById("employeeDetail").innerHTML = html;
|
||||
document.getElementById("detailTab").style.display = "block";
|
||||
showTab("detail");
|
||||
} catch (error) {
|
||||
alert('従業員情報の取得に失敗しました');
|
||||
alert("従業員情報の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>給与管理 - 給与設定</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<style>
|
||||
.employee-select {
|
||||
margin-bottom: 20px;
|
||||
@@ -20,7 +20,8 @@
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-group input, .form-group select {
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -46,7 +47,8 @@
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
table th, table td {
|
||||
table th,
|
||||
table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
@@ -67,21 +69,32 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="settingsContent" style="display:none;">
|
||||
<div id="settingsContent" style="display: none">
|
||||
<h2>給与設定履歴</h2>
|
||||
<button class="btn btn-primary" onclick="showAddForm()">新規設定追加</button>
|
||||
<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;">
|
||||
<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>
|
||||
<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">
|
||||
<input type="number" name="hourly_rate" step="0.01" />
|
||||
<small>時給制の場合に入力してください</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -105,27 +118,51 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>通勤手当 (円)</label>
|
||||
<input type="number" name="commute_allowance" step="0.01" value="0">
|
||||
<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">
|
||||
<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>
|
||||
<input type="date" name="valid_from" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用終了日</label>
|
||||
<input type="date" name="valid_to">
|
||||
<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>
|
||||
<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;">
|
||||
<div
|
||||
id="currentSetting"
|
||||
style="
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #e7f3ff;
|
||||
border-radius: 5px;
|
||||
"
|
||||
>
|
||||
<h3>現在の給与設定</h3>
|
||||
<div id="currentSettingContent"></div>
|
||||
</div>
|
||||
@@ -133,36 +170,46 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:8000';
|
||||
const API_BASE = "";
|
||||
let currentEmployeeId = null;
|
||||
|
||||
async function loadEmployees() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/employees/?is_active=true`);
|
||||
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('');
|
||||
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('従業員一覧の取得に失敗しました');
|
||||
alert("従業員一覧の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSalarySettings() {
|
||||
const employeeId = document.getElementById('employeeSelect').value;
|
||||
const employeeId = document.getElementById("employeeSelect").value;
|
||||
if (!employeeId) {
|
||||
document.getElementById('settingsContent').style.display = 'none';
|
||||
document.getElementById("settingsContent").style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
currentEmployeeId = employeeId;
|
||||
document.getElementById('settingsContent').style.display = 'block';
|
||||
document.getElementById("settingsContent").style.display = "block";
|
||||
|
||||
try {
|
||||
// 給与設定履歴を取得
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/salary/${employeeId}`);
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/settings/salary/${employeeId}`,
|
||||
);
|
||||
const settings = await response.json();
|
||||
|
||||
const html = `
|
||||
@@ -179,67 +226,77 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${settings.map(s => `
|
||||
${settings
|
||||
.map(
|
||||
(s) => `
|
||||
<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>${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.payment_type}</td>
|
||||
<td>¥${Number(s.commute_allowance).toLocaleString()}</td>
|
||||
<td>¥${Number(s.other_allowance).toLocaleString()}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('settingsList').innerHTML = html;
|
||||
document.getElementById("settingsList").innerHTML = html;
|
||||
|
||||
// 現在の設定を取得
|
||||
loadCurrentSetting(employeeId);
|
||||
} catch (error) {
|
||||
alert('給与設定の取得に失敗しました');
|
||||
alert("給与設定の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCurrentSetting(employeeId) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/salary/${employeeId}/current`);
|
||||
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.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><strong>適用期間:</strong> ${setting.valid_from} ~ ${setting.valid_to || "現在"}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('currentSettingContent').innerHTML = html;
|
||||
document.getElementById("currentSettingContent").innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('currentSettingContent').innerHTML = '<p>給与設定が登録されていません</p>';
|
||||
document.getElementById("currentSettingContent").innerHTML =
|
||||
"<p>給与設定が登録されていません</p>";
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('currentSettingContent').innerHTML = '<p>給与設定の取得に失敗しました</p>';
|
||||
document.getElementById("currentSettingContent").innerHTML =
|
||||
"<p>給与設定の取得に失敗しました</p>";
|
||||
}
|
||||
}
|
||||
|
||||
function showAddForm() {
|
||||
document.getElementById('addForm').style.display = 'block';
|
||||
document.getElementById("addForm").style.display = "block";
|
||||
// デフォルト値設定
|
||||
document.querySelector('[name="valid_from"]').value = new Date().toISOString().split('T')[0];
|
||||
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();
|
||||
document.getElementById("addForm").style.display = "none";
|
||||
document.querySelector("#addForm form").reset();
|
||||
}
|
||||
|
||||
async function saveSalarySetting(event) {
|
||||
@@ -265,21 +322,21 @@
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/settings/salary`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data)
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('給与設定を保存しました');
|
||||
alert("給与設定を保存しました");
|
||||
hideAddForm();
|
||||
loadSalarySettings();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('保存に失敗しました: ' + error.detail);
|
||||
alert("保存に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('保存に失敗しました');
|
||||
alert("保存に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>給与管理 - 設定</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<style>
|
||||
.nav-tabs {
|
||||
display: flex;
|
||||
@@ -37,7 +37,8 @@
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.form-group input, .form-group select {
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
@@ -63,7 +64,8 @@
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
table th, table td {
|
||||
table th,
|
||||
table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
@@ -78,17 +80,30 @@
|
||||
<h1>給与管理 - 設定</h1>
|
||||
|
||||
<div class="nav-tabs">
|
||||
<button class="nav-tab active" onclick="showTab('insurance')">保険料率</button>
|
||||
<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>
|
||||
<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;">
|
||||
<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">
|
||||
@@ -103,26 +118,44 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>従業員負担率 (%) *</label>
|
||||
<input type="number" name="employee_rate" step="0.0001" required placeholder="例: 5.0 (5%)">
|
||||
<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%)">
|
||||
<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>
|
||||
<input type="date" name="valid_from" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>適用終了日</label>
|
||||
<input type="date" name="valid_to">
|
||||
<input type="date" name="valid_to" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>備考</label>
|
||||
<input type="text" name="notes">
|
||||
<input type="text" name="notes" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">登録</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="hideInsuranceForm()">キャンセル</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
onclick="hideInsuranceForm()"
|
||||
>
|
||||
キャンセル
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +163,7 @@
|
||||
<!-- 所得税率表タブ -->
|
||||
<div id="tab-tax" class="tab-content">
|
||||
<h2>所得税率表</h2>
|
||||
<div class="form-group" style="max-width:300px;">
|
||||
<div class="form-group" style="max-width: 300px">
|
||||
<label>扶養人数でフィルター</label>
|
||||
<select id="filterDependents" onchange="loadIncomeTax()">
|
||||
<option value="">全て</option>
|
||||
@@ -142,42 +175,59 @@
|
||||
<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)">
|
||||
<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';
|
||||
const API_BASE = "";
|
||||
|
||||
function showTab(tabName) {
|
||||
document.querySelectorAll('.nav-tab').forEach(tab => tab.classList.remove('active'));
|
||||
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
||||
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');
|
||||
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');
|
||||
} 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';
|
||||
document.getElementById("insuranceForm").style.display = "block";
|
||||
}
|
||||
|
||||
function hideInsuranceForm() {
|
||||
document.getElementById('insuranceForm').style.display = 'none';
|
||||
document.querySelector('#insuranceForm form').reset();
|
||||
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 response = await fetch(
|
||||
`${API_BASE}/payroll/settings/insurance-rates`,
|
||||
);
|
||||
const rates = await response.json();
|
||||
|
||||
const html = `
|
||||
@@ -192,22 +242,26 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rates.map(rate => `
|
||||
${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>
|
||||
<td>${rate.valid_from} ~ ${rate.valid_to || "現在"}</td>
|
||||
<td>${rate.notes || "-"}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('insuranceList').innerHTML = html;
|
||||
document.getElementById("insuranceList").innerHTML = html;
|
||||
} catch (error) {
|
||||
alert('保険料率の取得に失敗しました');
|
||||
alert("保険料率の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@@ -227,32 +281,40 @@
|
||||
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)
|
||||
});
|
||||
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('保険料率を登録しました');
|
||||
alert("保険料率を登録しました");
|
||||
hideInsuranceForm();
|
||||
loadInsuranceRates();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('登録に失敗しました: ' + error.detail);
|
||||
alert("登録に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('登録に失敗しました');
|
||||
alert("登録に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadIncomeTax() {
|
||||
const dependentsFilter = document.getElementById('filterDependents').value;
|
||||
const params = dependentsFilter ? `?dependents_count=${dependentsFilter}` : '';
|
||||
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 response = await fetch(
|
||||
`${API_BASE}/payroll/settings/income-tax${params}`,
|
||||
);
|
||||
const taxes = await response.json();
|
||||
|
||||
const html = `
|
||||
@@ -267,22 +329,26 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${taxes.map(tax => `
|
||||
${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>
|
||||
<td>${tax.valid_from} ~ ${tax.valid_to || "現在"}</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
|
||||
document.getElementById('taxList').innerHTML = html;
|
||||
document.getElementById("taxList").innerHTML = html;
|
||||
} catch (error) {
|
||||
alert('所得税率表の取得に失敗しました');
|
||||
alert("所得税率表の取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@@ -292,16 +358,22 @@
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append("file", file);
|
||||
|
||||
const validFrom = prompt('適用開始日を入力してください (YYYY-MM-DD):', new Date().toISOString().split('T')[0]);
|
||||
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
|
||||
});
|
||||
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();
|
||||
@@ -309,14 +381,14 @@
|
||||
loadIncomeTax();
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert('インポートに失敗しました: ' + error.detail);
|
||||
alert("インポートに失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert('インポートに失敗しました');
|
||||
alert("インポートに失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
event.target.value = '';
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
// 初期化
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>給与管理システム</title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/style.css" />
|
||||
<style>
|
||||
.menu-grid {
|
||||
display: grid;
|
||||
@@ -42,56 +42,92 @@
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
||||
|
||||
<div class="menu-grid">
|
||||
<div class="menu-card" onclick="location.href='payroll-employees.html'">
|
||||
<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="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="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="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;">
|
||||
<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>
|
||||
<li><strong>月次給与計算</strong>で勤怠情報を入力し、給与を計算・承認します</li>
|
||||
<li>
|
||||
<strong>税率・保険料設定</strong
|
||||
>で社会保険料率や所得税率表を確認・更新します
|
||||
</li>
|
||||
<li>
|
||||
<strong>月次給与計算</strong
|
||||
>で勤怠情報を入力し、給与を計算・承認します
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,26 +12,9 @@ server {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API requests to backend container
|
||||
location /payroll/ {
|
||||
proxy_pass http://backend:18080/payroll/;
|
||||
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 all API requests to backend
|
||||
location ~ ^/(accounts|journals|ledger|trial-balance|opening-balances|general-ledger|payroll|users|month-locks|year-locks|cash|file-upload) {
|
||||
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;
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<div id="result"></div>
|
||||
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
const API = "";
|
||||
let accounts = [];
|
||||
|
||||
// ------------------------------------
|
||||
|
||||
@@ -115,6 +115,22 @@
|
||||
</style>
|
||||
</head>
|
||||
<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>
|
||||
|
||||
<div class="row">
|
||||
@@ -295,9 +311,7 @@
|
||||
<div id="printArea"></div>
|
||||
|
||||
<script>
|
||||
let rowSeq = 0; // 各行唯一ID
|
||||
|
||||
const API = "http://127.0.0.1:18080";
|
||||
const API = "";
|
||||
|
||||
// 千分位カンマフォーマット関数
|
||||
function formatNumberInput(input) {
|
||||
@@ -909,7 +923,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const API_BASE = "http://127.0.0.1:18080";
|
||||
const API_BASE = "";
|
||||
|
||||
async function callApi(path) {
|
||||
try {
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
const API = "";
|
||||
|
||||
async function search() {
|
||||
const from = document.getElementById("fromDate").value;
|
||||
|
||||
@@ -54,12 +54,25 @@
|
||||
</table>
|
||||
|
||||
<div style="margin-top: 16px">
|
||||
<button onclick="window.close()">閉じる</button>
|
||||
<button onclick="goBack()">一覧に戻る</button>
|
||||
<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>
|
||||
|
||||
<script>
|
||||
const API = "http://127.0.0.1:18080";
|
||||
const API = "";
|
||||
|
||||
async function loadJournalEntry() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
@@ -40,7 +40,22 @@
|
||||
<br /><br />
|
||||
|
||||
<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/journal.js"></script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const API_BASE = "http://127.0.0.1:18080";
|
||||
const API_BASE = "";
|
||||
|
||||
async function apiGet(path, params = {}) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
|
||||
@@ -42,7 +42,7 @@ async function submitJournal() {
|
||||
}
|
||||
|
||||
try {
|
||||
await fetch("http://127.0.0.1:18080/journals", {
|
||||
await fetch(`/journals`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -127,7 +127,7 @@ function recalcTotals() {
|
||||
document.getElementById("totalDebit").innerText = formatNumber(finalDebit);
|
||||
document.getElementById("totalCredit").innerText = formatNumber(finalCredit);
|
||||
document.getElementById("diff").innerText = formatNumber(
|
||||
finalDebit - finalCredit
|
||||
finalDebit - finalCredit,
|
||||
);
|
||||
|
||||
// 小計行を更新
|
||||
@@ -144,7 +144,7 @@ function updateSubtotalRows() {
|
||||
debit: sum.debit + Number(acc.opening_debit || 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;
|
||||
retainedEarnings = accounts.find(
|
||||
(a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益"
|
||||
(a) => a.account_name === "繰越利益剰余金" || a.account_name === "繰越利益",
|
||||
);
|
||||
|
||||
const tbody = document.querySelector("#balanceTable tbody");
|
||||
@@ -224,7 +224,7 @@ async function loadOpeningBalances() {
|
||||
debit: sum.debit + Number(acc.opening_debit || 0),
|
||||
credit: sum.credit + Number(acc.opening_credit || 0),
|
||||
}),
|
||||
{ debit: 0, credit: 0 }
|
||||
{ debit: 0, credit: 0 },
|
||||
);
|
||||
|
||||
const totalTr = document.createElement("tr");
|
||||
@@ -243,14 +243,12 @@ async function loadOpeningBalances() {
|
||||
});
|
||||
|
||||
// 年度锁定チェック
|
||||
fetch(
|
||||
`http://127.0.0.1:18080/opening-balances/lock-status?fiscal_year=${year}`
|
||||
)
|
||||
fetch(`/opening-balances/lock-status?fiscal_year=${year}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
isLocked = data.is_locked;
|
||||
document.querySelector(
|
||||
"button[onclick='saveOpeningBalances()']"
|
||||
"button[onclick='saveOpeningBalances()']",
|
||||
).disabled = isLocked;
|
||||
if (isLocked) {
|
||||
alert("この会計年度の期首残高は確定されています。");
|
||||
@@ -273,7 +271,7 @@ async function saveOpeningBalances() {
|
||||
(a) =>
|
||||
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) => ({
|
||||
account_id: a.account_id,
|
||||
@@ -282,7 +280,7 @@ async function saveOpeningBalances() {
|
||||
}));
|
||||
|
||||
try {
|
||||
await fetch("http://127.0.0.1:18080/opening-balances", {
|
||||
await fetch(`/opening-balances`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
|
||||
@@ -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月開始~現在月末)
|
||||
function getDefaultPeriod() {
|
||||
@@ -28,7 +34,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const lastDayOfMonth = new Date(fiscalEndYear, currentMonth, 0).getDate();
|
||||
const dateTo = `${fiscalEndYear}-${String(currentMonth).padStart(
|
||||
2,
|
||||
"0"
|
||||
"0",
|
||||
)}-${String(lastDayOfMonth).padStart(2, "0")}`;
|
||||
|
||||
return { dateFrom, dateTo };
|
||||
@@ -39,8 +45,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const url = `${API_URL}?date_from=${dateFrom}&date_to=${dateTo}`;
|
||||
console.log("読み込み URL:", url);
|
||||
|
||||
// 加載中のステータス表示
|
||||
const loadingEl = document.getElementById("loadingStatus");
|
||||
if (loadingEl) loadingEl.style.display = "inline";
|
||||
|
||||
fetch(url)
|
||||
.then((r) => {
|
||||
console.log("API Response Status:", r.status);
|
||||
if (!r.ok) throw new Error("API 返回错误: " + r.status);
|
||||
return r.json();
|
||||
})
|
||||
@@ -116,10 +127,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
totalClosing.toLocaleString();
|
||||
document.getElementById("totalRatio").textContent =
|
||||
data.totals?.ratio ?? "100.00";
|
||||
|
||||
// 加載中ステータスを非表示
|
||||
const loadingEl = document.getElementById("loadingStatus");
|
||||
if (loadingEl) loadingEl.style.display = "none";
|
||||
})
|
||||
.catch((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決算に対応)
|
||||
try {
|
||||
const defaultPeriod = getDefaultPeriod();
|
||||
document.getElementById("dateFrom").value = defaultPeriod.dateFrom;
|
||||
document.getElementById("dateTo").value = defaultPeriod.dateTo;
|
||||
|
||||
loadTrialBalance(defaultPeriod.dateFrom, defaultPeriod.dateTo);
|
||||
});
|
||||
console.log("✓ 初期読み込み完了");
|
||||
} catch (err) {
|
||||
console.error("初期読み込みエラー:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// 複数の方式で初期化を実行(iOS互換性を向上)
|
||||
document.addEventListener("DOMContentLoaded", initTrialBalance);
|
||||
window.addEventListener("load", initTrialBalance);
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initTrialBalance);
|
||||
} else {
|
||||
// ページがすでに読み込まれている場合、すぐに実行
|
||||
setTimeout(initTrialBalance, 100);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,22 @@
|
||||
<tbody id="ledger-body"></tbody>
|
||||
</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/ledger.js"></script>
|
||||
|
||||
@@ -40,7 +40,22 @@
|
||||
<br />
|
||||
|
||||
<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/opening_balance.js"></script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -100,7 +100,7 @@
|
||||
</table>
|
||||
|
||||
<script>
|
||||
const API = "http://127.0.0.1:8000";
|
||||
const API = "";
|
||||
|
||||
// --------------------
|
||||
// 初期化
|
||||
@@ -140,7 +140,7 @@
|
||||
});
|
||||
|
||||
document.getElementById("totalBalance").innerText = Number(
|
||||
data.total_balance
|
||||
data.total_balance,
|
||||
).toLocaleString();
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
const sel = document.getElementById("accountSelect");
|
||||
sel.innerHTML = data.items
|
||||
.map(
|
||||
(i) => `<option value="${i.account_id}">${i.account_name}</option>`
|
||||
(i) => `<option value="${i.account_id}">${i.account_name}</option>`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
|
||||
@@ -160,7 +160,22 @@
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -229,7 +229,22 @@
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -71,7 +71,22 @@
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -171,7 +171,22 @@
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -55,7 +55,21 @@
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<p>従業員の給与計算、扶養管理、保険料・税金の管理を行います</p>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>試算表</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'MS Gothic', 'Meiryo', sans-serif;
|
||||
font-family: "MS Gothic", "Meiryo", sans-serif;
|
||||
margin: 24px auto;
|
||||
font-size: 15px;
|
||||
max-width: 1400px;
|
||||
@@ -39,7 +39,7 @@
|
||||
.search-form button {
|
||||
padding: 6px 20px;
|
||||
margin: 0 5px;
|
||||
background: #4CAF50;
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
@@ -50,7 +50,7 @@
|
||||
background: #45a049;
|
||||
}
|
||||
.search-form button.secondary {
|
||||
background: #2196F3;
|
||||
background: #2196f3;
|
||||
}
|
||||
.search-form button.secondary:hover {
|
||||
background: #0b7dda;
|
||||
@@ -96,8 +96,31 @@
|
||||
</style>
|
||||
</head>
|
||||
<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>
|
||||
|
||||
<div
|
||||
id="loadingStatus"
|
||||
style="display: none; color: #666; font-size: 12px; margin: 10px 0"
|
||||
>
|
||||
読み込み中...
|
||||
</div>
|
||||
|
||||
<div class="search-form">
|
||||
<label for="dateFrom">開始年月日:</label>
|
||||
<input type="date" id="dateFrom" min="1900-01-01" max="2099-12-31" />
|
||||
@@ -111,7 +134,7 @@
|
||||
|
||||
<div class="period-info">
|
||||
<span id="periodInfo">令和7年 1月 1日 ~ 令和7年 12月31日</span>
|
||||
<span style="margin-left: 20px;">単位: 円</span>
|
||||
<span style="margin-left: 20px">単位: 円</span>
|
||||
</div>
|
||||
|
||||
<table id="trialBalanceTable">
|
||||
@@ -142,8 +165,29 @@
|
||||
</table>
|
||||
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = "http://localhost:8000";
|
||||
const API_BASE = "";
|
||||
|
||||
function addTestResult(message, status = "info") {
|
||||
const div = document.getElementById("testResults");
|
||||
|
||||
Reference in New Issue
Block a user