From 78615ec6eea4ec0dd420b4c01fb00cd05ab086c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Fri, 27 Feb 2026 16:56:28 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[payroll]=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=EC=97=90=EC=84=9C=20=EA=B3=B5=EC=A0=9C?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=20=EC=9E=90=EB=8F=99=20=EC=9E=AC=EA=B3=84?= =?UTF-8?q?=EC=82=B0=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 모드(editingPayrollId)에서는 /calculate API 호출 생략 - 기본급 등 변경 시 총지급액/실수령액만 로컬 재합산 - 재계산 버튼 클릭 시에만 최신 요율로 서버 계산 실행 --- resources/views/hr/payrolls/index.blade.php | 88 +++++++++++++++++++-- 1 file changed, 82 insertions(+), 6 deletions(-) diff --git a/resources/views/hr/payrolls/index.blade.php b/resources/views/hr/payrolls/index.blade.php index aca3dcba..a4c14a75 100644 --- a/resources/views/hr/payrolls/index.blade.php +++ b/resources/views/hr/payrolls/index.blade.php @@ -535,6 +535,28 @@ function recalculate() { } function doRecalculate() { + const baseSalary = parseMoneyValue(document.getElementById('payrollBaseSalary')); + const overtimePay = parseMoneyValue(document.getElementById('payrollOvertimePay')); + const bonus = parseMoneyValue(document.getElementById('payrollBonus')); + + let allowancesTotal = 0; + document.querySelectorAll('#allowancesContainer > div').forEach(row => { + allowancesTotal += parseMoneyValue(row.querySelector('.allowance-amount')); + }); + + const grossSalary = baseSalary + overtimePay + bonus + allowancesTotal; + const taxableBase = grossSalary - bonus; + + document.getElementById('calcGross').textContent = numberFormat(grossSalary); + document.getElementById('calcTaxableBase').textContent = numberFormat(taxableBase); + + // 수정 모드: 공제항목은 기존 저장값 유지, 총지급액/실수령액만 재합산 + if (editingPayrollId) { + updateDeductionTotals(); + return; + } + + // 신규 등록 모드: 서버에서 요율 기반 자동 계산 const allowances = []; document.querySelectorAll('#allowancesContainer > div').forEach(row => { const name = row.querySelector('.allowance-name').value; @@ -550,9 +572,9 @@ function doRecalculate() { }); const data = { - base_salary: parseMoneyValue(document.getElementById('payrollBaseSalary')), - overtime_pay: parseMoneyValue(document.getElementById('payrollOvertimePay')), - bonus: parseMoneyValue(document.getElementById('payrollBonus')), + base_salary: baseSalary, + overtime_pay: overtimePay, + bonus: bonus, allowances: allowances, deductions: deductions, user_id: parseInt(document.getElementById('payrollUserId').value) || null, @@ -583,8 +605,6 @@ function doRecalculate() { const el = document.getElementById(id); if (!el.dataset.manual) setMoneyValue(el, val); }); - document.getElementById('calcGross').textContent = numberFormat(d.gross_salary); - document.getElementById('calcTaxableBase').textContent = numberFormat(d.taxable_base); if (d.family_count) { const fcEl = document.getElementById('calcFamilyCount'); if (fcEl) fcEl.textContent = d.family_count + '명'; @@ -621,7 +641,63 @@ function resetDeductionOverrides() { el.classList.remove('bg-yellow-50', 'border-yellow-300'); el.classList.add('border-gray-200'); }); - recalculate(); + // 재계산 버튼: 수정 모드에서도 서버 API 호출하여 최신 요율로 재계산 + forceRecalculate(); + } + + function forceRecalculate() { + const baseSalary = parseMoneyValue(document.getElementById('payrollBaseSalary')); + const overtimePay = parseMoneyValue(document.getElementById('payrollOvertimePay')); + const bonus = parseMoneyValue(document.getElementById('payrollBonus')); + + const allowances = []; + document.querySelectorAll('#allowancesContainer > div').forEach(row => { + const name = row.querySelector('.allowance-name').value; + const amount = parseMoneyValue(row.querySelector('.allowance-amount')); + if (name && amount > 0) allowances.push({name, amount}); + }); + + const deductions = []; + document.querySelectorAll('#deductionsContainer > div').forEach(row => { + const name = row.querySelector('.deduction-name').value; + const amount = parseMoneyValue(row.querySelector('.deduction-amount')); + if (name && amount !== 0) deductions.push({name, amount}); + }); + + const data = { + base_salary: baseSalary, overtime_pay: overtimePay, bonus: bonus, + allowances: allowances, deductions: deductions, + user_id: parseInt(document.getElementById('payrollUserId').value) || null, + }; + + fetch('{{ route("api.admin.hr.payrolls.calculate") }}', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Accept': 'application/json' }, + body: JSON.stringify(data), + }) + .then(r => r.json()) + .then(result => { + if (result.success) { + const d = result.data; + const fields = { + 'calcPension': d.pension, 'calcHealth': d.health_insurance, + 'calcLongTermCare': d.long_term_care, 'calcEmployment': d.employment_insurance, + 'calcIncomeTax': d.income_tax, 'calcResidentTax': d.resident_tax, + }; + Object.entries(fields).forEach(([id, val]) => { + const el = document.getElementById(id); + if (!el.dataset.manual) setMoneyValue(el, val); + }); + document.getElementById('calcGross').textContent = numberFormat(d.gross_salary); + document.getElementById('calcTaxableBase').textContent = numberFormat(d.taxable_base); + if (d.family_count) { + const fcEl = document.getElementById('calcFamilyCount'); + if (fcEl) fcEl.textContent = d.family_count + '명'; + } + updateDeductionTotals(); + } + }) + .catch(console.error); } function updateDeductionTotals() {