feat: [leave] 잔여연차 퇴사자 포함 및 퇴사일 기준 연차 계산
- getBalanceSummary에 resigned 상태 포함 - 퇴사자 연차는 퇴사일까지만 산출 - 퇴사자 1년 미만 재계산 대상 제외 - 상태 컬럼 추가 (재직/휴직/퇴사 배지) - 퇴사자 행 회색 배경 시각적 구분 - 근속 계산: 퇴사자는 입사일~퇴사일 기준
This commit is contained in:
@@ -232,11 +232,11 @@ public function getBalanceSummary(?int $year = null, ?string $sort = null, ?stri
|
||||
// (1) 테넌트 연차 정책 조회
|
||||
$policy = LeavePolicy::forTenant($tenantId)->first();
|
||||
|
||||
// (2) 재직/휴직 직원 전체 조회
|
||||
// (2) 재직/휴직/퇴사 직원 전체 조회
|
||||
$employees = Employee::query()
|
||||
->with(['user:id,name', 'department:id,name'])
|
||||
->forTenant($tenantId)
|
||||
->whereIn('employee_status', ['active', 'leave'])
|
||||
->whereIn('employee_status', ['active', 'leave', 'resigned'])
|
||||
->get();
|
||||
|
||||
// (3) 기존 balance 일괄 조회
|
||||
@@ -277,6 +277,7 @@ public function getBalanceSummary(?int $year = null, ?string $sort = null, ?stri
|
||||
$employeesByUserId = $employees->keyBy('user_id');
|
||||
|
||||
// (6) 현재연도 + 1년 미만 직원은 매번 재계산 (월별 발생 방식)
|
||||
// 퇴사자는 퇴사일 기준으로 확정되므로 재계산 대상에서 제외
|
||||
if ($year === (int) now()->year) {
|
||||
foreach ($existingBalances as $balance) {
|
||||
$employee = $employeesByUserId->get($balance->user_id);
|
||||
@@ -284,6 +285,10 @@ public function getBalanceSummary(?int $year = null, ?string $sort = null, ?stri
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($employee->employee_status === 'resigned') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hire = Carbon::parse($employee->hire_date);
|
||||
if ($hire->diffInMonths(today()) < 12) {
|
||||
$recalculated = $this->calculateAnnualLeaveDays($employee, $year, $policy);
|
||||
@@ -311,6 +316,10 @@ public function getBalanceSummary(?int $year = null, ?string $sort = null, ?stri
|
||||
'name' => fn ($b) => $b->employee?->display_name ?? '',
|
||||
'department' => fn ($b) => $b->employee?->department?->name ?? '',
|
||||
'hire_date' => fn ($b) => $b->employee?->hire_date ?? '9999-12-31',
|
||||
'status' => fn ($b) => match ($b->employee?->employee_status) {
|
||||
'active' => 1, 'leave' => 2, 'resigned' => 3, default => 9,
|
||||
},
|
||||
'resign_date' => fn ($b) => $b->employee?->resign_date ?? '9999-12-31',
|
||||
'total_days' => fn ($b) => $b->total_days,
|
||||
'used_days' => fn ($b) => $b->used_days,
|
||||
'remaining' => fn ($b) => $b->total_days - $b->used_days,
|
||||
@@ -345,6 +354,15 @@ private function calculateAnnualLeaveDays(Employee $employee, int $year, ?LeaveP
|
||||
? today()
|
||||
: Carbon::create($year, 12, 31);
|
||||
|
||||
// 퇴사자: 기준일을 퇴사일로 제한
|
||||
$resignDate = $employee->resign_date;
|
||||
if ($resignDate) {
|
||||
$resign = Carbon::parse($resignDate);
|
||||
if ($resign->lessThan($referenceDate)) {
|
||||
$referenceDate = $resign;
|
||||
}
|
||||
}
|
||||
|
||||
// 아직 입사 전이면 0일
|
||||
if ($hire->greaterThan($referenceDate)) {
|
||||
return 0;
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
$sortColumns = [
|
||||
'name' => ['label' => '사원', 'align' => 'left'],
|
||||
'department' => ['label' => '부서', 'align' => 'left'],
|
||||
'status' => ['label' => '상태', 'align' => 'center'],
|
||||
'hire_date' => ['label' => '입사일', 'align' => 'center'],
|
||||
'resign_date' => ['label' => '퇴사일', 'align' => 'center', 'sortable' => false],
|
||||
'resign_date' => ['label' => '퇴사일', 'align' => 'center'],
|
||||
'tenure' => ['label' => '근속', 'align' => 'center', 'sortable' => false],
|
||||
'total_days' => ['label' => '부여', 'align' => 'center'],
|
||||
'used_days' => ['label' => '사용', 'align' => 'center'],
|
||||
@@ -60,25 +61,29 @@ class="inline-flex items-center gap-1 hover:text-blue-600 transition-colors">
|
||||
$displayName = $employee?->display_name ?? $balance->user?->name ?? '-';
|
||||
$department = $employee?->department;
|
||||
$hireDate = $employee?->hire_date;
|
||||
$isResigned = $employee?->employee_status === 'resigned';
|
||||
$isLeave = $employee?->employee_status === 'leave';
|
||||
$tenureParts = null;
|
||||
if ($hireDate) {
|
||||
$hire = \Carbon\Carbon::parse($hireDate);
|
||||
$tenureYears = (int) $hire->diffInYears(now());
|
||||
$tenureMonths = (int) $hire->copy()->addYears($tenureYears)->diffInMonths(now());
|
||||
// 퇴사자: 입사일~퇴사일, 재직자: 입사일~오늘
|
||||
$tenureEnd = ($isResigned && $employee?->resign_date) ? \Carbon\Carbon::parse($employee->resign_date) : now();
|
||||
$tenureYears = (int) $hire->diffInYears($tenureEnd);
|
||||
$tenureMonths = (int) $hire->copy()->addYears($tenureYears)->diffInMonths($tenureEnd);
|
||||
$tenureParts = ['years' => $tenureYears, 'months' => $tenureMonths];
|
||||
}
|
||||
$remaining = $balance->total_days - $balance->used_days;
|
||||
$rate = $balance->total_days > 0 ? round(($balance->used_days / $balance->total_days) * 100) : 0;
|
||||
$barColor = $rate >= 90 ? 'bg-red-500' : ($rate >= 70 ? 'bg-amber-500' : 'bg-blue-500');
|
||||
@endphp
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<tr class="{{ $isResigned ? 'bg-gray-100 opacity-75' : 'hover:bg-gray-50' }} transition-colors">
|
||||
{{-- 사원 --}}
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="shrink-0 w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center text-xs font-medium">
|
||||
<div class="shrink-0 w-8 h-8 rounded-full {{ $isResigned ? 'bg-gray-200 text-gray-500' : 'bg-blue-100 text-blue-600' }} flex items-center justify-center text-xs font-medium">
|
||||
{{ mb_substr($displayName, 0, 1) }}
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-900">{{ $displayName }}</span>
|
||||
<span class="text-sm font-medium {{ $isResigned ? 'text-gray-500' : 'text-gray-900' }}">{{ $displayName }}</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -87,6 +92,17 @@ class="inline-flex items-center gap-1 hover:text-blue-600 transition-colors">
|
||||
{{ $department?->name ?? '-' }}
|
||||
</td>
|
||||
|
||||
{{-- 상태 --}}
|
||||
<td class="px-6 py-4 whitespace-nowrap text-center">
|
||||
@if($isResigned)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-200 text-gray-600">퇴사</span>
|
||||
@elseif($isLeave)
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700">휴직</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700">재직</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- 입사일 --}}
|
||||
<td class="px-6 py-4 whitespace-nowrap text-center text-sm text-gray-700">
|
||||
{{ $hireDate ?? '-' }}
|
||||
@@ -143,7 +159,7 @@ class="inline-flex items-center gap-1 hover:text-blue-600 transition-colors">
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="9" class="px-6 py-12 text-center">
|
||||
<td colspan="10" class="px-6 py-12 text-center">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<svg class="w-12 h-12 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 7h6m0 10v-3m-3 3h.01M9 17h.01M9 14h.01M12 14h.01M15 11h.01M12 11h.01M9 11h.01M7 21h10a2 2 0 002-2V5a2 2 0 00-2-2H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
||||
|
||||
Reference in New Issue
Block a user