修正
This commit is contained in:
@@ -195,14 +195,22 @@
|
||||
<div id="tab-salary" class="tab-content-calc active">
|
||||
<div class="filters">
|
||||
<label>
|
||||
対象年月:
|
||||
対象年:
|
||||
<input
|
||||
type="month"
|
||||
id="filterYearMonth"
|
||||
style="width: 160px"
|
||||
placeholder="YYYY-MM"
|
||||
type="number"
|
||||
id="filterYear"
|
||||
style="width: 90px"
|
||||
value="2026"
|
||||
min="2000"
|
||||
max="2099"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
従業員:
|
||||
<select id="filterEmployee" style="min-width: 200px">
|
||||
<option value="">全員</option>
|
||||
</select>
|
||||
</label>
|
||||
<button class="btn btn-primary" onclick="loadPayrolls()">検索</button>
|
||||
<button class="btn btn-success" onclick="showCalculateForm()">
|
||||
新規計算
|
||||
@@ -373,6 +381,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>計算オプション</h3>
|
||||
<div style="display: flex; gap: 30px; padding: 8px 0">
|
||||
<label
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
<input type="checkbox" id="calcIncomeTax" checked />
|
||||
所得税を計算する
|
||||
</label>
|
||||
<label
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
"
|
||||
>
|
||||
<input type="checkbox" id="calcSocialInsurance" checked />
|
||||
社会保険を計算する
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">計算実行</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -566,11 +602,66 @@
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>開始年月 *</label>
|
||||
<input type="month" id="voucherStartYearMonth" required />
|
||||
<div style="display: flex; gap: 6px; align-items: center">
|
||||
<input
|
||||
type="text"
|
||||
id="voucherStartYear"
|
||||
style="width: 90px"
|
||||
placeholder="年"
|
||||
maxlength="4"
|
||||
pattern="[0-9]{4}"
|
||||
inputmode="numeric"
|
||||
required
|
||||
/>
|
||||
<span style="color: #666">年</span>
|
||||
<select id="voucherStartMonth" required>
|
||||
<option value="">月</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>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
</select>
|
||||
<span style="color: #666">月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>終了年月</label>
|
||||
<input type="month" id="voucherEndYearMonth" />
|
||||
<div style="display: flex; gap: 6px; align-items: center">
|
||||
<input
|
||||
type="text"
|
||||
id="voucherEndYear"
|
||||
style="width: 90px"
|
||||
placeholder="年"
|
||||
maxlength="4"
|
||||
pattern="[0-9]{4}"
|
||||
inputmode="numeric"
|
||||
/>
|
||||
<span style="color: #666">年</span>
|
||||
<select id="voucherEndMonth">
|
||||
<option value="">月</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>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
</select>
|
||||
<span style="color: #666">月</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label style="color: #999; font-size: 12px">
|
||||
@@ -799,24 +890,41 @@
|
||||
const bonusSelect = document.getElementById("bonusEmployeeSelect");
|
||||
if (bonusSelect) bonusSelect.innerHTML = optionsHtml;
|
||||
|
||||
// 検索用フィルタのドロップダウンも更新
|
||||
const filterSelect = document.getElementById("filterEmployee");
|
||||
if (filterSelect) {
|
||||
const currentVal = filterSelect.value;
|
||||
filterSelect.innerHTML =
|
||||
'<option value="">全員</option>' +
|
||||
employees
|
||||
.map(
|
||||
(emp) =>
|
||||
`<option value="${emp.employee_id}">${emp.employee_code} - ${emp.name}</option>`,
|
||||
)
|
||||
.join("");
|
||||
filterSelect.value = currentVal;
|
||||
}
|
||||
|
||||
return employees;
|
||||
}
|
||||
|
||||
async function loadPayrolls() {
|
||||
const ym = document.getElementById("filterYearMonth").value;
|
||||
let year = null;
|
||||
let month = null;
|
||||
if (ym) {
|
||||
const parts = ym.split("-");
|
||||
if (parts.length === 2) {
|
||||
year = Number(parts[0]);
|
||||
month = Number(parts[1]);
|
||||
}
|
||||
// 前回の給与明細をクリア
|
||||
const detailDiv = document.getElementById("payrollDetail");
|
||||
if (detailDiv) {
|
||||
detailDiv.style.display = "none";
|
||||
detailDiv.innerHTML = "";
|
||||
}
|
||||
|
||||
const year =
|
||||
Number(document.getElementById("filterYear")?.value) || null;
|
||||
const employeeId =
|
||||
document.getElementById("filterEmployee")?.value || null;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (year) params.append("payroll_year", year);
|
||||
if (month) params.append("payroll_month", month);
|
||||
if (employeeId) params.append("employee_id", employeeId);
|
||||
|
||||
// employeeMap が空なら読み込む
|
||||
if (!employeeMap || Object.keys(employeeMap).length === 0) {
|
||||
await loadEmployees();
|
||||
@@ -827,52 +935,66 @@
|
||||
`${API_BASE}/payroll/calculation/?${params}`,
|
||||
);
|
||||
const payrolls = await response.json();
|
||||
const html = payrolls
|
||||
.map((p) => {
|
||||
const emp = employeeMap[p.employee_id];
|
||||
const empLabel = emp
|
||||
? `従業員ID: ${p.employee_id} | ${emp.employee_code} - ${emp.name}`
|
||||
: `従業員ID: ${p.employee_id}`;
|
||||
|
||||
if (!payrolls || payrolls.length === 0) {
|
||||
document.getElementById("payrollList").innerHTML =
|
||||
"<p>給与データがありません</p>";
|
||||
return;
|
||||
}
|
||||
|
||||
// 月別にグループ化
|
||||
const byMonth = {};
|
||||
payrolls.forEach((p) => {
|
||||
const key = `${p.payroll_year}-${String(p.payroll_month).padStart(2, "0")}`;
|
||||
if (!byMonth[key]) byMonth[key] = [];
|
||||
byMonth[key].push(p);
|
||||
});
|
||||
|
||||
// 月順にソート
|
||||
const sortedMonths = Object.keys(byMonth).sort();
|
||||
|
||||
const html = sortedMonths
|
||||
.map((monthKey) => {
|
||||
const [y, m] = monthKey.split("-");
|
||||
const items = byMonth[monthKey]
|
||||
.map((p) => {
|
||||
const emp = employeeMap[p.employee_id];
|
||||
const empLabel = emp
|
||||
? `${emp.employee_code} - ${emp.name}`
|
||||
: `従業員ID: ${p.employee_id}`;
|
||||
return `
|
||||
<div class="payroll-item" style="display:flex; justify-content:space-between; align-items:flex-start;">
|
||||
<div style="flex:1; cursor:pointer;" onclick="viewPayroll(${p.payroll_id})">
|
||||
<div>
|
||||
<strong>${empLabel}</strong>
|
||||
<span class="status-badge status-${p.status}">${getStatusLabel(p.status)}</span>
|
||||
<span style="margin-left:8px; font-size:12px; color:#666;">支給日: ${p.payment_date}</span>
|
||||
</div>
|
||||
<div style="margin-top:4px;">
|
||||
<strong>差引支給額: ¥${Number(p.net_payment).toLocaleString()}</strong>
|
||||
<span style="color:#666; font-size:13px;">(総支給: ¥${Number(p.total_payment).toLocaleString()} - 控除: ¥${Number(p.total_deduction).toLocaleString()})</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-small" onclick="deletePayroll(${p.payroll_id}); event.stopPropagation();" style="margin-left:10px; white-space:nowrap; background-color:#dc3545; color:#fff; border:none;">
|
||||
削除
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<div class="payroll-item" style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div style="flex: 1; cursor: pointer;" onclick="viewPayroll(${
|
||||
p.payroll_id
|
||||
})">
|
||||
<div>
|
||||
<strong>${p.payroll_year}年${
|
||||
p.payroll_month
|
||||
}月</strong>
|
||||
<span class="status-badge status-${
|
||||
p.status
|
||||
}">${getStatusLabel(p.status)}</span>
|
||||
</div>
|
||||
<div>
|
||||
${empLabel} | 支給日: ${p.payment_date}
|
||||
</div>
|
||||
<div>
|
||||
<strong>差引支給額: ¥${Number(
|
||||
p.net_payment,
|
||||
).toLocaleString()}</strong>
|
||||
(総支給: ¥${Number(
|
||||
p.total_payment,
|
||||
).toLocaleString()} - 控除: ¥${Number(
|
||||
p.total_deduction,
|
||||
).toLocaleString()})
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger btn-small" onclick="deletePayroll(${
|
||||
p.payroll_id
|
||||
}); event.stopPropagation();" style="margin-left: 10px; white-space: nowrap; background-color: #dc3545; color: #fff; border: none;">
|
||||
削除
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
<div style="margin-bottom:20px;">
|
||||
<div style="background:#e8f0fe; border-left:4px solid #007bff; padding:8px 14px; font-weight:bold; font-size:15px; margin-bottom:6px;">
|
||||
${Number(y)}年 ${Number(m)}月
|
||||
</div>
|
||||
${items}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
document.getElementById("payrollList").innerHTML =
|
||||
html || "<p>給与データがありません</p>";
|
||||
document.getElementById("payrollList").innerHTML = html;
|
||||
} catch (error) {
|
||||
alert("給与一覧の取得に失敗しました");
|
||||
console.error(error);
|
||||
@@ -927,6 +1049,11 @@
|
||||
// ensure payroll_year/month are numbers
|
||||
data.payroll_year = valid.year;
|
||||
data.payroll_month = valid.month;
|
||||
// 計算オプションフラグ
|
||||
data.calc_income_tax =
|
||||
document.getElementById("calcIncomeTax")?.checked ?? true;
|
||||
data.calc_social_insurance =
|
||||
document.getElementById("calcSocialInsurance")?.checked ?? true;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
@@ -942,6 +1069,7 @@
|
||||
const result = await response.json();
|
||||
alert("給与計算が完了しました");
|
||||
closeCalculateForm();
|
||||
await loadPayrolls();
|
||||
viewPayroll(result.payroll_id);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
@@ -1188,6 +1316,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h3>計算オプション</h3>
|
||||
<div style="display:flex; gap:30px; padding:8px 0;">
|
||||
<label style="display:flex; align-items:center; gap:6px; cursor:pointer;">
|
||||
<input type="checkbox" id="editCalcIncomeTax" checked />
|
||||
所得税を計算する
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:6px; cursor:pointer;">
|
||||
<input type="checkbox" id="editCalcSocialInsurance" checked />
|
||||
社会保険を計算する
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">保存して再計算</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="this.closest('div').remove()">キャンセル</button>
|
||||
</form>
|
||||
@@ -1224,11 +1366,20 @@
|
||||
);
|
||||
|
||||
if (response.ok) {
|
||||
// チェックボックスの値をフォーム削除前に取得
|
||||
const calcIncomeTax =
|
||||
document.getElementById("editCalcIncomeTax")?.checked ?? true;
|
||||
const calcSocialInsurance =
|
||||
document.getElementById("editCalcSocialInsurance")?.checked ??
|
||||
true;
|
||||
alert("給与データを更新しました");
|
||||
form.closest("div").remove();
|
||||
|
||||
// 再計算
|
||||
await recalculatePayroll(payrollId);
|
||||
await recalculatePayroll(payrollId, {
|
||||
calc_income_tax: calcIncomeTax,
|
||||
calc_social_insurance: calcSocialInsurance,
|
||||
});
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("更新に失敗しました: " + error.detail);
|
||||
@@ -1266,14 +1417,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function recalculatePayroll(payrollId) {
|
||||
async function recalculatePayroll(payrollId, calcOptions = null) {
|
||||
if (!confirm("給与を再計算しますか?")) return;
|
||||
|
||||
try {
|
||||
const body = calcOptions || {};
|
||||
const response = await fetch(
|
||||
`${API_BASE}/payroll/calculation/${payrollId}/recalculate`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1320,11 +1474,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 初期化: 月ピッカーにセットして一覧を読み込む
|
||||
// 初期化: 年フィルタに現在年をセットして一覧を読み込む
|
||||
const now = new Date();
|
||||
const ym =
|
||||
now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0");
|
||||
document.getElementById("filterYearMonth").value = ym;
|
||||
const filterYearEl = document.getElementById("filterYear");
|
||||
if (filterYearEl) filterYearEl.value = now.getFullYear();
|
||||
document.getElementById("filterBonusYearMonth").value = ym;
|
||||
loadPayrolls();
|
||||
|
||||
@@ -1359,8 +1514,11 @@
|
||||
now.getFullYear() +
|
||||
"-" +
|
||||
String(now.getMonth() + 1).padStart(2, "0");
|
||||
document.getElementById("voucherStartYearMonth").value = ym;
|
||||
document.getElementById("voucherEndYearMonth").value = "";
|
||||
document.getElementById("voucherStartYear").value = now.getFullYear();
|
||||
document.getElementById("voucherStartMonth").value =
|
||||
now.getMonth() + 1;
|
||||
document.getElementById("voucherEndYear").value = "";
|
||||
document.getElementById("voucherEndMonth").value = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1732,8 +1890,13 @@
|
||||
|
||||
async function printVouchers() {
|
||||
// 検証
|
||||
const startYM = document.getElementById("voucherStartYearMonth").value;
|
||||
if (!startYM) {
|
||||
const startYear = Number(
|
||||
document.getElementById("voucherStartYear").value,
|
||||
);
|
||||
const startMonth = Number(
|
||||
document.getElementById("voucherStartMonth").value,
|
||||
);
|
||||
if (!startYear || !startMonth) {
|
||||
alert("開始年月を選択してください");
|
||||
return;
|
||||
}
|
||||
@@ -1745,11 +1908,10 @@
|
||||
}
|
||||
|
||||
// 年月を解析
|
||||
const [startYear, startMonth] = startYM.split("-").map(Number);
|
||||
const endYM = document.getElementById("voucherEndYearMonth").value;
|
||||
const endParts = endYM ? endYM.split("-").map(Number) : null;
|
||||
const endYear = endParts ? endParts[0] : null;
|
||||
const endMonth = endParts ? endParts[1] : null;
|
||||
const endYearVal = document.getElementById("voucherEndYear").value;
|
||||
const endMonthVal = document.getElementById("voucherEndMonth").value;
|
||||
const endYear = endYearVal ? Number(endYearVal) : null;
|
||||
const endMonth = endMonthVal ? Number(endMonthVal) : null;
|
||||
|
||||
const voucherType = document.querySelector(
|
||||
"input[name='voucherType']:checked",
|
||||
@@ -1900,6 +2062,7 @@
|
||||
<div class="page-title">${salary.payroll_year}年${String(salary.payroll_month).padStart(2, "0")}月 給与明細</div>
|
||||
<div class="employee-info">従業員: ${emp.employee_code} ${emp.employee_name}</div>
|
||||
<div class="employee-info" style="color: #666; font-size: 12px;">支給日: ${paymentDate}</div>
|
||||
${emp.employment_type ? `<div class="employee-info" style="color: #666; font-size: 12px;">雇用形態:${emp.employment_type}</div>` : ""}
|
||||
|
||||
<div class="section-title">勤怠情報</div>
|
||||
<table class="detail-table">
|
||||
@@ -2013,6 +2176,7 @@
|
||||
<div class="page-title">${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月 賞与明細${bonus.bonus_type ? ` (${bonus.bonus_type})` : ""}</div>
|
||||
<div class="employee-info">従業員: ${emp.employee_code} ${emp.employee_name}</div>
|
||||
<div class="employee-info" style="color: #666; font-size: 12px;">支給日: ${bonusPaymentDate}</div>
|
||||
${emp.employment_type ? `<div class="employee-info" style="color: #666; font-size: 12px;">雇用形態:${emp.employment_type}</div>` : ""}
|
||||
|
||||
<div class="section-title">支給項目</div>
|
||||
<table class="detail-table">
|
||||
@@ -2103,8 +2267,13 @@
|
||||
|
||||
async function exportVouchersCSV() {
|
||||
// 検証
|
||||
const startYM = document.getElementById("voucherStartYearMonth").value;
|
||||
if (!startYM) {
|
||||
const startYear = Number(
|
||||
document.getElementById("voucherStartYear").value,
|
||||
);
|
||||
const startMonth = Number(
|
||||
document.getElementById("voucherStartMonth").value,
|
||||
);
|
||||
if (!startYear || !startMonth) {
|
||||
alert("開始年月を選択してください");
|
||||
return;
|
||||
}
|
||||
@@ -2115,11 +2284,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const [startYear, startMonth] = startYM.split("-").map(Number);
|
||||
const endYM = document.getElementById("voucherEndYearMonth").value;
|
||||
const endParts = endYM ? endYM.split("-").map(Number) : null;
|
||||
const endYear = endParts ? endParts[0] : null;
|
||||
const endMonth = endParts ? endParts[1] : null;
|
||||
const endYearVal = document.getElementById("voucherEndYear").value;
|
||||
const endMonthVal = document.getElementById("voucherEndMonth").value;
|
||||
const endYear = endYearVal ? Number(endYearVal) : null;
|
||||
const endMonth = endMonthVal ? Number(endMonthVal) : null;
|
||||
const voucherType = document.querySelector(
|
||||
"input[name='voucherType']:checked",
|
||||
).value;
|
||||
|
||||
Reference in New Issue
Block a user