修正
This commit is contained in:
@@ -29,10 +29,17 @@
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.card h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.card p:last-child {
|
||||
margin-top: auto;
|
||||
margin-bottom: 0;
|
||||
padding-top: 12px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 14px;
|
||||
@@ -40,6 +47,7 @@
|
||||
background: #007bff;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
header {
|
||||
max-width: 980px;
|
||||
|
||||
@@ -861,6 +861,7 @@
|
||||
<div class="search-buttons">
|
||||
<button onclick="searchJournals()">🔍 検索</button>
|
||||
<button onclick="printSearchResults()">🖨️ 印刷</button>
|
||||
<button onclick="clearSearchConditions()" style="background: #6c757d; color: white; border: none; padding: 6px 14px; border-radius: 4px; cursor: pointer;">🔄 条件クリア</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2394,6 +2395,8 @@
|
||||
});
|
||||
|
||||
const printArea = document.getElementById("printArea");
|
||||
// 印刷後に元の検索結果を復元できるよう保存
|
||||
const savedSearchHtml = printArea.innerHTML;
|
||||
printArea.innerHTML = "";
|
||||
|
||||
// 月ごとにデータをグループ化
|
||||
@@ -2525,6 +2528,13 @@
|
||||
|
||||
printArea.appendChild(section);
|
||||
});
|
||||
// 印刷後に元の検索結果を復元する
|
||||
const restoreAfterPrint = () => {
|
||||
printArea.innerHTML = savedSearchHtml;
|
||||
window.removeEventListener("afterprint", restoreAfterPrint);
|
||||
};
|
||||
window.addEventListener("afterprint", restoreAfterPrint);
|
||||
|
||||
// 少し待ってから印刷(データの表示を待つ)
|
||||
setTimeout(() => {
|
||||
window.print();
|
||||
@@ -2552,6 +2562,25 @@
|
||||
searchJournals();
|
||||
}
|
||||
|
||||
// 検索条件クリア(期間以外)
|
||||
function clearSearchConditions() {
|
||||
// 科目
|
||||
const accountInput = document.getElementById("searchAccountInput");
|
||||
if (accountInput) {
|
||||
accountInput.value = "";
|
||||
accountInput.dataset.accountId = "";
|
||||
}
|
||||
// 摘要
|
||||
const keyword = document.getElementById("searchKeyword");
|
||||
if (keyword) keyword.value = "";
|
||||
// 方向
|
||||
const side = document.getElementById("searchAccountSide");
|
||||
if (side) side.value = "";
|
||||
// 修正履歴を含む
|
||||
const includeHistory = document.getElementById("searchIncludeHistory");
|
||||
if (includeHistory) includeHistory.checked = false;
|
||||
}
|
||||
|
||||
// 检索结果を表に表示
|
||||
async function searchJournals() {
|
||||
try {
|
||||
@@ -2784,14 +2813,70 @@
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// 日付をコピー
|
||||
document.getElementById("entryDate").value = data.entry_date;
|
||||
// 行数で振り分け: 3行以上 → 複数行詳細フォーム、2行以下 → 簡易フォーム
|
||||
if (data.lines && data.lines.length > 2) {
|
||||
copyJournalToDetailForm(data);
|
||||
} else {
|
||||
copyJournalToSimpleForm(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showAlert(`エラーが発生しました: ${error.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
// 摘要をコピー
|
||||
document.getElementById("description").value = data.description || "";
|
||||
// 複数行フォームへのコピー
|
||||
function copyJournalToDetailForm(data) {
|
||||
document.getElementById("detailEntryDate").value = data.entry_date;
|
||||
document.getElementById("detailDescription").value = data.description || "";
|
||||
|
||||
// 新UIでは簡易的にフォームに反映(最初の借方行の科目 + 最初の貸方行を取引手段に設定)
|
||||
if (data.lines && data.lines.length > 0) {
|
||||
// 既存行をクリア
|
||||
document.getElementById("detailLinesTbody").innerHTML = "";
|
||||
detailRowSeq = 0;
|
||||
|
||||
// 各明細行をコピー
|
||||
data.lines.forEach((line) => {
|
||||
const tr = detailAddLine();
|
||||
const accountInput = tr.querySelector(".detail-account-input");
|
||||
const acc = accounts.find((a) => a.account_id === line.account_id);
|
||||
if (acc) {
|
||||
accountInput.value = `${acc.account_code} ${acc.account_name}`;
|
||||
accountInput.dataset.accountId = acc.account_id;
|
||||
}
|
||||
if (Number(line.debit) > 0) {
|
||||
tr.querySelector(".detail-debit").value = Number(line.debit).toLocaleString();
|
||||
}
|
||||
if (Number(line.credit) > 0) {
|
||||
tr.querySelector(".detail-credit").value = Number(line.credit).toLocaleString();
|
||||
}
|
||||
if (line.line_description) {
|
||||
tr.querySelector(".detail-line-memo").value = line.line_description;
|
||||
}
|
||||
});
|
||||
|
||||
detailUpdateTotals();
|
||||
|
||||
// 詳細セクションを開いてスクロール
|
||||
const detailSection = document.getElementById("detailSection");
|
||||
detailSection.open = true;
|
||||
detailSection.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
|
||||
showAlert(
|
||||
`仕訳を複製しました(複数行モード)。\n\n※ このコピーは新規仕訳として独立した記録になります。\n※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
||||
"success",
|
||||
);
|
||||
}
|
||||
|
||||
// 簡易フォームへのコピー(従来ロジック)
|
||||
function copyJournalToSimpleForm(data) {
|
||||
// 日付をコピー
|
||||
document.getElementById("entryDate").value = data.entry_date;
|
||||
|
||||
// 摘要をコピー
|
||||
document.getElementById("description").value = data.description || "";
|
||||
|
||||
// 新UIでは簡易的にフォームに反映(最初の借方行の科目 + 最初の貸方行を取引手段に設定)
|
||||
if (data.lines && data.lines.length > 0) {
|
||||
// 借方と貸方を分類
|
||||
const debitLines = data.lines.filter((l) => Number(l.debit) > 0);
|
||||
const creditLines = data.lines.filter((l) => Number(l.credit) > 0);
|
||||
@@ -2961,10 +3046,6 @@
|
||||
`仕訳を複製しました。\n\n※ このコピーは新規仕訳として独立した記録になります。\n※ 日付と摘要を必要に応じて修正してから登録してください。`,
|
||||
"success",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showAlert(`エラーが発生しました: ${error.message}`, "error");
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
content="no-cache, no-store, must-revalidate"
|
||||
/>
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<script src="js/auth.js"></script>
|
||||
@@ -241,36 +244,54 @@
|
||||
<div id="tab-salary" class="tab-content-calc active">
|
||||
<div class="split-layout">
|
||||
<div class="split-left">
|
||||
<div class="filters" style="display:flex; flex-direction:column; gap:8px;">
|
||||
<label style="display:flex; align-items:center; gap:8px;">
|
||||
<span style="white-space:nowrap; min-width:60px;">対象年:</span>
|
||||
<input
|
||||
type="number"
|
||||
id="filterYear"
|
||||
style="width: 90px"
|
||||
value="2026"
|
||||
min="2000"
|
||||
max="2099"
|
||||
/>
|
||||
</label>
|
||||
<label style="display:flex; align-items:center; gap:8px;">
|
||||
<span style="white-space:nowrap; min-width:60px;">従業員:</span>
|
||||
<select id="filterEmployee" style="flex:1;">
|
||||
<option value="">全員</option>
|
||||
</select>
|
||||
</label>
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button class="btn btn-primary" onclick="loadPayrolls()">検索</button>
|
||||
<button class="btn btn-success" onclick="showCalculateForm()">新規計算</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="filters"
|
||||
style="display: flex; flex-direction: column; gap: 8px"
|
||||
>
|
||||
<label style="display: flex; align-items: center; gap: 8px">
|
||||
<span style="white-space: nowrap; min-width: 60px"
|
||||
>対象年:</span
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
id="filterYear"
|
||||
style="width: 90px"
|
||||
value="2026"
|
||||
min="2000"
|
||||
max="2099"
|
||||
/>
|
||||
</label>
|
||||
<label style="display: flex; align-items: center; gap: 8px">
|
||||
<span style="white-space: nowrap; min-width: 60px"
|
||||
>従業員:</span
|
||||
>
|
||||
<select id="filterEmployee" style="flex: 1">
|
||||
<option value="">全員</option>
|
||||
</select>
|
||||
</label>
|
||||
<div style="display: flex; gap: 8px">
|
||||
<button class="btn btn-primary" onclick="loadPayrolls()">
|
||||
検索
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="showCalculateForm()">
|
||||
新規計算
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="payrollList" class="payroll-list"></div>
|
||||
</div><!-- /split-left -->
|
||||
<div id="payrollList" class="payroll-list"></div>
|
||||
</div>
|
||||
<!-- /split-left -->
|
||||
<div class="split-right" id="splitRightSalary">
|
||||
<div id="payrollDetail" class="payroll-detail" style="display:none"></div>
|
||||
</div><!-- /split-right -->
|
||||
</div><!-- /split-layout -->
|
||||
<div
|
||||
id="payrollDetail"
|
||||
class="payroll-detail"
|
||||
style="display: none"
|
||||
></div>
|
||||
</div>
|
||||
<!-- /split-right -->
|
||||
</div>
|
||||
<!-- /split-layout -->
|
||||
|
||||
<!-- 給与計算フォーム(モーダル風) -->
|
||||
<div
|
||||
@@ -472,37 +493,45 @@
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 賞与計算タブ -->
|
||||
<div id="tab-bonus" class="tab-content-calc">
|
||||
<div class="split-layout">
|
||||
<div class="split-left">
|
||||
<div class="filters">
|
||||
<label>
|
||||
対象年:
|
||||
<input
|
||||
type="number"
|
||||
id="filterBonusYear"
|
||||
style="width: 90px"
|
||||
value="2026"
|
||||
min="2000"
|
||||
max="2099"
|
||||
/>
|
||||
</label>
|
||||
<button class="btn btn-primary" onclick="loadBonus()">検索</button>
|
||||
<button class="btn btn-success" onclick="showBonusForm()">
|
||||
新規計算
|
||||
</button>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<label>
|
||||
対象年:
|
||||
<input
|
||||
type="number"
|
||||
id="filterBonusYear"
|
||||
style="width: 90px"
|
||||
value="2026"
|
||||
min="2000"
|
||||
max="2099"
|
||||
/>
|
||||
</label>
|
||||
<button class="btn btn-primary" onclick="loadBonus()">
|
||||
検索
|
||||
</button>
|
||||
<button class="btn btn-success" onclick="showBonusForm()">
|
||||
新規計算
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="bonusList" class="payroll-list"></div>
|
||||
</div><!-- /split-left -->
|
||||
<div id="bonusList" class="payroll-list"></div>
|
||||
</div>
|
||||
<!-- /split-left -->
|
||||
<div class="split-right" id="splitRightBonus">
|
||||
<div id="bonusDetail" class="payroll-detail" style="display:none"></div>
|
||||
</div><!-- /split-right -->
|
||||
</div><!-- /split-layout -->
|
||||
<div
|
||||
id="bonusDetail"
|
||||
class="payroll-detail"
|
||||
style="display: none"
|
||||
></div>
|
||||
</div>
|
||||
<!-- /split-right -->
|
||||
</div>
|
||||
<!-- /split-layout -->
|
||||
|
||||
<!-- 賞与計算フォーム -->
|
||||
<div
|
||||
@@ -637,7 +666,6 @@
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 账票出力タブ -->
|
||||
@@ -1750,20 +1778,64 @@
|
||||
|
||||
// 計算明細表示用の変数(賞与は総支給額に料率を乗じる)
|
||||
const _bHealth = Number(bonus.health_insurance || 0);
|
||||
|
||||
// 源泉税控除対象人数を計算(給与と同じロジック)
|
||||
const bDependentCount = employee.dependents
|
||||
? employee.dependents.filter((d) => {
|
||||
if (d.valid_to && new Date(d.valid_to) < new Date()) return false;
|
||||
if (!d.birth_date) return false;
|
||||
const birthDate = new Date(d.birth_date);
|
||||
const targetYear = bonus.bonus_year;
|
||||
const yearEnd = new Date(targetYear, 11, 31);
|
||||
let age = yearEnd.getFullYear() - birthDate.getFullYear();
|
||||
const monthDiff = yearEnd.getMonth() - birthDate.getMonth();
|
||||
if (monthDiff < 0 || (monthDiff === 0 && yearEnd.getDate() < birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return age >= 16;
|
||||
}).length
|
||||
: 0;
|
||||
const _bCare = Number(bonus.care_insurance || 0);
|
||||
const _bPension = Number(bonus.pension_insurance || 0);
|
||||
const _bEmp = Number(bonus.employment_insurance || 0);
|
||||
const _bChild = Number(bonus.child_support || 0);
|
||||
const _bTotal = Number(bonus.total_bonus || 0);
|
||||
const _bHRateFull = _bTotal > 0 && _bHealth > 0 ? (((_bHealth * 2) / _bTotal) * 100).toFixed(3) : "-";
|
||||
const _bHRateHalf = _bTotal > 0 && _bHealth > 0 ? ((_bHealth / _bTotal) * 100).toFixed(3) : "-";
|
||||
const _bCRateFull = _bTotal > 0 && _bCare > 0 ? (((_bCare * 2) / _bTotal) * 100).toFixed(3) : null;
|
||||
const _bCRateHalf = _bTotal > 0 && _bCare > 0 ? ((_bCare / _bTotal) * 100).toFixed(3) : null;
|
||||
const _bPRateFull = _bTotal > 0 && _bPension > 0 ? (((_bPension * 2) / _bTotal) * 100).toFixed(3) : "-";
|
||||
const _bPRateHalf = _bTotal > 0 && _bPension > 0 ? ((_bPension / _bTotal) * 100).toFixed(3) : "-";
|
||||
const _bEmpRate = _bTotal > 0 && _bEmp > 0 ? ((_bEmp / _bTotal) * 100).toFixed(3) : "-";
|
||||
const _bCSRateFull = _bTotal > 0 && _bChild > 0 ? (((_bChild * 2) / _bTotal) * 100).toFixed(4) : null;
|
||||
const _bCSRateHalf = _bTotal > 0 && _bChild > 0 ? ((_bChild / _bTotal) * 100).toFixed(4) : null;
|
||||
const _bHRateFull =
|
||||
_bTotal > 0 && _bHealth > 0
|
||||
? (((_bHealth * 2) / _bTotal) * 100).toFixed(3)
|
||||
: "-";
|
||||
const _bHRateHalf =
|
||||
_bTotal > 0 && _bHealth > 0
|
||||
? ((_bHealth / _bTotal) * 100).toFixed(3)
|
||||
: "-";
|
||||
const _bCRateFull =
|
||||
_bTotal > 0 && _bCare > 0
|
||||
? (((_bCare * 2) / _bTotal) * 100).toFixed(3)
|
||||
: null;
|
||||
const _bCRateHalf =
|
||||
_bTotal > 0 && _bCare > 0
|
||||
? ((_bCare / _bTotal) * 100).toFixed(3)
|
||||
: null;
|
||||
const _bPRateFull =
|
||||
_bTotal > 0 && _bPension > 0
|
||||
? (((_bPension * 2) / _bTotal) * 100).toFixed(3)
|
||||
: "-";
|
||||
const _bPRateHalf =
|
||||
_bTotal > 0 && _bPension > 0
|
||||
? ((_bPension / _bTotal) * 100).toFixed(3)
|
||||
: "-";
|
||||
const _bEmpRate =
|
||||
_bTotal > 0 && _bEmp > 0
|
||||
? ((_bEmp / _bTotal) * 100).toFixed(3)
|
||||
: "-";
|
||||
const _bCSRateFull =
|
||||
_bTotal > 0 && _bChild > 0
|
||||
? (((_bChild * 2) / _bTotal) * 100).toFixed(4)
|
||||
: null;
|
||||
const _bCSRateHalf =
|
||||
_bTotal > 0 && _bChild > 0
|
||||
? ((_bChild / _bTotal) * 100).toFixed(4)
|
||||
: null;
|
||||
const _bSocialSub = _bHealth + _bCare + _bPension + _bEmp + _bChild;
|
||||
const _bTaxable = _bTotal - _bSocialSub;
|
||||
|
||||
@@ -1815,9 +1887,10 @@
|
||||
</div>
|
||||
<div class="detail-row"><span>雇用保険:</span><span>¥${_bEmp.toLocaleString()}</span></div>
|
||||
<div class="calc-note">
|
||||
${_bEmp > 0
|
||||
? `¥${_bTotal.toLocaleString()}(賞与総支給額)× ${_bEmpRate}%(雇用保険料率・従業員負担分)`
|
||||
: "賞与に対する雇用保険は別途計算方式による(または0円)"
|
||||
${
|
||||
_bEmp > 0
|
||||
? `¥${_bTotal.toLocaleString()}(賞与総支給額)× ${_bEmpRate}%(雇用保険料率・従業員負担分)`
|
||||
: "賞与に対する雇用保険は別途計算方式による(または0円)"
|
||||
}
|
||||
</div>
|
||||
<div class="detail-row"><span>子ども・子育て支援金:</span><span>¥${_bChild.toLocaleString()}</span></div>
|
||||
@@ -1829,12 +1902,13 @@
|
||||
}
|
||||
</div>
|
||||
<div class="detail-row"><span style="font-weight:bold">社会保険小計:</span><span style="font-weight:bold">¥${_bSocialSub.toLocaleString()}</span></div>
|
||||
<div class="detail-row"><span>所得税:</span><span>¥${Number(
|
||||
bonus.income_tax || 0,
|
||||
).toLocaleString()}</span></div>
|
||||
<div class="detail-row">
|
||||
<span>所得税 <small style="color:#666;">(甲欄${bDependentCount}人)</small>:</span>
|
||||
<span>¥${Number(bonus.income_tax || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
<div class="calc-note">
|
||||
課税対象額 = 賞与総支給額 ¥${_bTotal.toLocaleString()} − 社会保険料計 ¥${_bSocialSub.toLocaleString()} = ¥${_bTaxable.toLocaleString()}<br>
|
||||
→ 賞与に対する源泉徴収税額の算出率の表を参照して計算
|
||||
→ 賞与に対する源泉徴収税額の算出率の表(甲欄・扶養${bDependentCount}人)を参照して計算
|
||||
</div>
|
||||
<div class="detail-row"><span>住民税:</span><span>¥${Number(
|
||||
bonus.resident_tax || 0,
|
||||
@@ -1856,6 +1930,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="editBonus(${
|
||||
bonus.bonus_id
|
||||
})">編集</button>
|
||||
<button class="btn btn-primary" onclick="recalculateBonus(${
|
||||
bonus.bonus_id
|
||||
})">再計算</button>
|
||||
@@ -1872,6 +1949,105 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function editBonus(bonusId) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/payroll/bonus/${bonusId}`);
|
||||
const bonus = await response.json();
|
||||
|
||||
const html = `
|
||||
<div style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); width: 600px; max-height: 90vh; overflow-y: auto; background: white; padding: 30px; border: 2px solid #007bff; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); z-index: 1000;">
|
||||
<h2>賞与データ編集</h2>
|
||||
<form id="editBonusForm" onsubmit="saveBonusEdit(event, ${bonusId})">
|
||||
<div class="detail-section">
|
||||
<h3>基本情報</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>支給日</label>
|
||||
<input type="date" name="payment_date" value="${bonus.payment_date}" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>賞与種別</label>
|
||||
<select name="bonus_type">
|
||||
<option value="夏季賞与" ${bonus.bonus_type === '夏季賞与' ? 'selected' : ''}>夏季賞与</option>
|
||||
<option value="冬季賞与" ${bonus.bonus_type === '冬季賞与' ? 'selected' : ''}>冬季賞与</option>
|
||||
<option value="決算賞与" ${bonus.bonus_type === '決算賞与' ? 'selected' : ''}>決算賞与</option>
|
||||
<option value="その他" ${bonus.bonus_type === 'その他' ? 'selected' : ''}>その他</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<h3>支給項目</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>基本賞与額</label>
|
||||
<input type="number" name="base_bonus" step="1" value="${bonus.base_bonus}" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>業績賞与</label>
|
||||
<input type="number" name="performance_bonus" step="1" value="${bonus.performance_bonus}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>その他賞与</label>
|
||||
<input type="number" name="other_bonus" step="1" value="${bonus.other_bonus}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<h3>控除項目(手動入力)</h3>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>その他控除</label>
|
||||
<input type="number" name="other_deduction" step="1" value="${bonus.other_deduction}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:12px; color:#666;">※保存後に再計算を行うと社会保険料・所得税が自動更新されます。</p>
|
||||
<button type="submit" class="btn btn-primary">保存して再計算</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="this.closest('div').parentElement.remove()">キャンセル</button>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const editDiv = document.createElement("div");
|
||||
editDiv.innerHTML = html;
|
||||
document.body.appendChild(editDiv);
|
||||
} catch (error) {
|
||||
alert("賞与データの取得に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveBonusEdit(event, bonusId) {
|
||||
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/bonus/${bonusId}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert("賞与データを更新しました");
|
||||
form.closest("div").parentElement.remove();
|
||||
await recalculateBonus(bonusId, true);
|
||||
} else {
|
||||
const error = await response.json();
|
||||
alert("更新に失敗しました: " + error.detail);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("更新に失敗しました");
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteBonus(bonusId) {
|
||||
if (
|
||||
!confirm("この賞与データを削除しますか?\nこの操作は取り消せません。")
|
||||
@@ -1899,8 +2075,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function recalculateBonus(bonusId) {
|
||||
if (!confirm("賞与を再計算しますか?")) return;
|
||||
async function recalculateBonus(bonusId, skipConfirm = false) {
|
||||
if (!skipConfirm && !confirm("賞与を再計算しますか?")) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
@@ -2270,10 +2446,14 @@
|
||||
bonuses.forEach((bonus) => {
|
||||
const bonusYearMonth = `${bonus.bonus_year}-${bonus.bonus_month}`;
|
||||
const currentKey_new = `${emp.employee_id}-${bonusYearMonth}-bonus`;
|
||||
const bonusPaymentDate = `${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月${getLastDayOfMonth(bonus.bonus_year, bonus.bonus_month)}日`;
|
||||
|
||||
// 新しいキーが異なる場合は新しいページを開く
|
||||
if (currentKey !== currentKey_new) {
|
||||
// DBに保存された実際の支給日を使用。なければ月末日を計算
|
||||
const bonusPaymentDate = bonus.payment_date
|
||||
? (() => {
|
||||
const d = new Date(bonus.payment_date);
|
||||
return `${d.getFullYear()}年${String(d.getMonth()+1).padStart(2,'0')}月${String(d.getDate()).padStart(2,'0')}日`;
|
||||
})()
|
||||
: `${bonus.bonus_year}年${String(bonus.bonus_month).padStart(2, "0")}月${getLastDayOfMonth(bonus.bonus_year, bonus.bonus_month)}日`;
|
||||
if (currentKey && currentKey !== currentKey_new) {
|
||||
if (pageOpen) {
|
||||
printHtml += "</div>";
|
||||
}
|
||||
|
||||
@@ -215,9 +215,13 @@
|
||||
<li>給与計算と賞与計算で異なる料率を設定できます</li>
|
||||
<li>事業所所在地(都道府県)ごとに設定できます</li>
|
||||
<li>介護保険の適用年齢は設定可能です(デフォルト: 40歳以上)</li>
|
||||
<li><strong>年度適用期間:</strong> 「年度」に設定した値は <strong>その年の4月〜翌年3月</strong> に適用されます。<br>
|
||||
例)2026年度設定 → 2026年4月〜2027年3月の計算に参照されます。<br>
|
||||
2026年1月〜3月分は 2025年度(rate_year=2025)の料率が参照されます。</li>
|
||||
<li>
|
||||
<strong>年度適用期間:</strong> 「年度」に設定した値は
|
||||
<strong>その年の4月〜翌年3月</strong> に適用されます。<br />
|
||||
例)2026年度設定 → 2026年4月〜2027年3月の計算に参照されます。<br />
|
||||
2026年1月〜3月分は
|
||||
2025年度(rate_year=2025)の料率が参照されます。
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -807,20 +811,34 @@
|
||||
|
||||
// メタデータに料率がない場合、データから逆算する
|
||||
if (rates.healthNoCareRate == null) {
|
||||
const refRow = data.find(r => Number(r.monthly_amount) > 0 && Number(r.health_insurance_no_care) > 0);
|
||||
const refRow = data.find(
|
||||
(r) =>
|
||||
Number(r.monthly_amount) > 0 &&
|
||||
Number(r.health_insurance_no_care) > 0,
|
||||
);
|
||||
if (refRow) {
|
||||
const ma = Number(refRow.monthly_amount);
|
||||
rates.healthNoCareRate = Number(refRow.health_insurance_no_care) / ma * 100;
|
||||
rates.healthWithCareRate = Number(refRow.health_insurance_with_care) / ma * 100;
|
||||
rates.healthNoCareRate =
|
||||
(Number(refRow.health_insurance_no_care) / ma) * 100;
|
||||
rates.healthWithCareRate =
|
||||
(Number(refRow.health_insurance_with_care) / ma) * 100;
|
||||
if (Number(refRow.child_support ?? 0) > 0) {
|
||||
rates.childSupportRate = Number(refRow.child_support) / ma * 100;
|
||||
rates.childSupportRate =
|
||||
(Number(refRow.child_support) / ma) * 100;
|
||||
}
|
||||
}
|
||||
// 厚生年金は低等級(1-3)で0になるため、pension_insurance > 0 の行を別途検索
|
||||
if (rates.pensionRate == null) {
|
||||
const pensionRefRow = data.find(r => Number(r.monthly_amount) > 0 && Number(r.pension_insurance ?? 0) > 0);
|
||||
const pensionRefRow = data.find(
|
||||
(r) =>
|
||||
Number(r.monthly_amount) > 0 &&
|
||||
Number(r.pension_insurance ?? 0) > 0,
|
||||
);
|
||||
if (pensionRefRow) {
|
||||
rates.pensionRate = Number(pensionRefRow.pension_insurance) / Number(pensionRefRow.monthly_amount) * 100;
|
||||
rates.pensionRate =
|
||||
(Number(pensionRefRow.pension_insurance) /
|
||||
Number(pensionRefRow.monthly_amount)) *
|
||||
100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user