From b7a7dfd04f941465e9bc4e8752493e8d6e0eb516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Thu, 12 Mar 2026 15:34:12 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[payroll]=20=EC=9E=85=EC=82=AC=EC=9B=94?= =?UTF-8?q?=20=EA=B8=89=EC=97=AC=20=EB=93=B1=EB=A1=9D=20=EC=8B=9C=20?= =?UTF-8?q?=EC=9D=BC=ED=95=A0=EA=B3=84=EC=82=B0=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사원 선택 시 입사일이 해당 급여월이면 일할계산 자동 적용 - 산식: 월액 / 해당월총일수 × 근무일수 (입사일 포함) - 기본급, 고정연장근로수당, 식대 모두 일할계산 - 일할계산 내역 안내 배너 표시 (산식, 금액 상세) - 자동 적용 후 수동 수정 가능 --- resources/views/hr/payrolls/index.blade.php | 109 +++++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/resources/views/hr/payrolls/index.blade.php b/resources/views/hr/payrolls/index.blade.php index 76f52752..915ec159 100644 --- a/resources/views/hr/payrolls/index.blade.php +++ b/resources/views/hr/payrolls/index.blade.php @@ -187,6 +187,7 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f data-base-salary="{{ $si['base_salary'] ?? 0 }}" data-overtime-pay="{{ $si['fixed_overtime_pay'] ?? 0 }}" data-meal-allowance="{{ $si['meal_allowance'] ?? 200000 }}" + data-hire-date="{{ $emp->getJsonExtraValue('hire_date', '') }}" data-dependents="{{ 1 + collect($emp->dependents)->where('is_dependent', true)->count() }}"> {{ $emp->display_name ?? $emp->user?->name }} @@ -206,6 +207,36 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 f + {{-- 일할계산 안내 (입사월인 경우) --}} + + {{-- 지급 항목 --}}

지급 항목

@@ -997,6 +1028,7 @@ function openCreatePayrollModal() { document.getElementById('payrollPayMonth').value = document.getElementById('payrollMonth').value; document.getElementById('allowancesContainer').innerHTML = ''; document.getElementById('deductionsContainer').innerHTML = ''; + hideProrataNotice(); resetCalculation(); document.getElementById('payrollModal').classList.remove('hidden'); } @@ -1055,6 +1087,58 @@ function closePayrollModal() { document.getElementById('payrollUserId').disabled = false; } + // ===== 일할계산 유틸 ===== + function getDaysInMonth(year, month) { + return new Date(year, month, 0).getDate(); + } + + function checkProrata(hireDate, payYear, payMonth) { + if (!hireDate) return null; + const d = new Date(hireDate); + if (d.getFullYear() === payYear && (d.getMonth() + 1) === payMonth) { + const totalDays = getDaysInMonth(payYear, payMonth); + const workDays = totalDays - d.getDate() + 1; + return { hireDate, totalDays, workDays, hireDay: d.getDate() }; + } + return null; + } + + function applyProrata(amount, totalDays, workDays) { + return Math.round(amount / totalDays * workDays); + } + + function showProrataNotice(prorata, baseSalary, overtimePay, mealAllowance) { + const notice = document.getElementById('prorataNotice'); + const desc = document.getElementById('prorataDesc'); + const tbody = document.getElementById('prorataTableBody'); + const total = document.getElementById('prorataTotal'); + + desc.textContent = `입사일: ${prorata.hireDate} | 근무일수: ${prorata.workDays}일 / ${prorata.totalDays}일`; + + const items = [ + { name: '기본급', amount: baseSalary }, + { name: '식대', amount: mealAllowance }, + { name: '고정연장근로수당', amount: overtimePay }, + ]; + let sum = 0; + tbody.innerHTML = items.map(item => { + const pro = applyProrata(item.amount, prorata.totalDays, prorata.workDays); + sum += pro; + return ` + ${item.name} + ${numberFormat(item.amount)} + ${numberFormat(item.amount)}/${prorata.totalDays}*${prorata.workDays} + ${numberFormat(pro)} + `; + }).join(''); + total.textContent = numberFormat(sum); + notice.classList.remove('hidden'); + } + + function hideProrataNotice() { + document.getElementById('prorataNotice').classList.add('hidden'); + } + // ===== 사원 선택 시 급여 산정값 자동 입력 + 가족수 표시 ===== document.getElementById('payrollUserId').addEventListener('change', function() { const selected = this.options[this.selectedIndex]; @@ -1062,17 +1146,34 @@ function closePayrollModal() { const fcEl = document.getElementById('calcFamilyCount'); if (fcEl) fcEl.textContent = dependents + '명'; + hideProrataNotice(); if (editingPayrollId) return; const baseSalary = parseInt(selected.dataset.baseSalary || 0); const overtimePay = parseInt(selected.dataset.overtimePay || 0); const mealAllowance = parseInt(selected.dataset.mealAllowance || 0); + const hireDate = selected.dataset.hireDate || ''; + + let finalBase = baseSalary; + let finalOT = overtimePay; + let finalMeal = mealAllowance; if (baseSalary > 0) { - // 연봉 산정 테이블에서 계산된 값 적용 - setMoneyValue(document.getElementById('payrollBaseSalary'), baseSalary); - setMoneyValue(document.getElementById('payrollOvertimePay'), overtimePay); - setMoneyValue(document.getElementById('payrollBonus'), mealAllowance); + // 일할계산 체크 + const payYear = parseInt(document.getElementById('payrollPayYear').value); + const payMonth = parseInt(document.getElementById('payrollPayMonth').value); + const prorata = checkProrata(hireDate, payYear, payMonth); + + if (prorata && prorata.workDays < prorata.totalDays) { + finalBase = applyProrata(baseSalary, prorata.totalDays, prorata.workDays); + finalOT = applyProrata(overtimePay, prorata.totalDays, prorata.workDays); + finalMeal = applyProrata(mealAllowance, prorata.totalDays, prorata.workDays); + showProrataNotice(prorata, baseSalary, overtimePay, mealAllowance); + } + + setMoneyValue(document.getElementById('payrollBaseSalary'), finalBase); + setMoneyValue(document.getElementById('payrollOvertimePay'), finalOT); + setMoneyValue(document.getElementById('payrollBonus'), finalMeal); } else { // fallback: 산정 데이터 없으면 연봉에서 단순 계산 const salary = parseInt(selected.dataset.salary || 0);