- 확정 상태에서 작성중으로 되돌리는 기능 추가
- Model: isUnconfirmable() 상태 헬퍼 추가
- Service: unconfirmPayroll() 메서드 추가
- Controller: unconfirm() 엔드포인트 추가
- Route: POST /{id}/unconfirm 라우트 추가
- View: 확정 취소 버튼 및 JS 함수 추가
197 lines
12 KiB
PHP
197 lines
12 KiB
PHP
{{-- 급여 목록 테이블 (HTMX로 로드) --}}
|
|
@php
|
|
use App\Models\HR\Payroll;
|
|
@endphp
|
|
|
|
<x-table-swipe>
|
|
<table class="min-w-full">
|
|
<thead class="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-600">사원</th>
|
|
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-600">부서</th>
|
|
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-600">기본급</th>
|
|
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-600">수당</th>
|
|
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-600">총지급액</th>
|
|
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-600">공제액</th>
|
|
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-600">실수령액</th>
|
|
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-600">상태</th>
|
|
<th class="px-4 py-3 text-center text-sm font-semibold text-gray-600">작업</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-100">
|
|
@forelse($payrolls as $payroll)
|
|
@php
|
|
$profile = $payroll->user?->tenantProfiles?->first();
|
|
$department = $profile?->department;
|
|
$displayName = $profile?->display_name ?? $payroll->user?->name ?? '-';
|
|
$color = Payroll::STATUS_COLORS[$payroll->status] ?? 'gray';
|
|
$label = Payroll::STATUS_MAP[$payroll->status] ?? $payroll->status;
|
|
$allowancesTotal = 0;
|
|
if ($payroll->allowances) {
|
|
foreach ($payroll->allowances as $a) { $allowancesTotal += ($a['amount'] ?? 0); }
|
|
}
|
|
$overtimeBonus = ($payroll->overtime_pay ?? 0) + ($payroll->bonus ?? 0) + $allowancesTotal;
|
|
@endphp
|
|
<tr class="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">
|
|
{{ mb_substr($displayName, 0, 1) }}
|
|
</div>
|
|
<span class="text-sm font-medium text-gray-900">{{ $displayName }}</span>
|
|
</div>
|
|
</td>
|
|
|
|
{{-- 부서 --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-700">
|
|
{{ $department?->name ?? '-' }}
|
|
</td>
|
|
|
|
{{-- 기본급 --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm text-gray-700">
|
|
{{ number_format($payroll->base_salary) }}
|
|
</td>
|
|
|
|
{{-- 수당 (고정연장근로+상여+기타) --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm text-gray-500">
|
|
{{ $overtimeBonus > 0 ? number_format($overtimeBonus) : '-' }}
|
|
</td>
|
|
|
|
{{-- 총지급액 --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium text-gray-800">
|
|
{{ number_format($payroll->gross_salary) }}
|
|
</td>
|
|
|
|
{{-- 공제액 --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm text-red-600">
|
|
{{ number_format($payroll->total_deductions) }}
|
|
</td>
|
|
|
|
{{-- 실수령액 --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium text-emerald-600">
|
|
{{ number_format($payroll->net_salary) }}
|
|
</td>
|
|
|
|
{{-- 상태 --}}
|
|
<td class="px-6 py-4 whitespace-nowrap text-center">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-{{ $color }}-100 text-{{ $color }}-700">
|
|
{{ $label }}
|
|
</span>
|
|
</td>
|
|
|
|
{{-- 작업 --}}
|
|
<td class="px-4 py-4 whitespace-nowrap text-center">
|
|
<div class="flex items-center justify-center gap-1">
|
|
{{-- 수정 (draft만) --}}
|
|
@if($payroll->isEditable())
|
|
<button type="button" onclick="openEditPayrollModal({{ $payroll->id }}, {{ json_encode([
|
|
'user_id' => $payroll->user_id,
|
|
'user_name' => $displayName,
|
|
'base_salary' => $payroll->base_salary,
|
|
'overtime_pay' => $payroll->overtime_pay,
|
|
'bonus' => $payroll->bonus,
|
|
'allowances' => $payroll->allowances,
|
|
'pension' => $payroll->pension,
|
|
'health_insurance' => $payroll->health_insurance,
|
|
'long_term_care' => $payroll->long_term_care,
|
|
'employment_insurance' => $payroll->employment_insurance,
|
|
'income_tax' => $payroll->income_tax,
|
|
'resident_tax' => $payroll->resident_tax,
|
|
'deductions' => $payroll->deductions,
|
|
'note' => $payroll->note,
|
|
]) }})" class="p-1 text-blue-600 hover:text-blue-800" title="수정">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
|
</svg>
|
|
</button>
|
|
<button type="button" onclick="deletePayroll({{ $payroll->id }})" class="p-1 text-red-600 hover:text-red-800" title="삭제">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
|
</svg>
|
|
</button>
|
|
@endif
|
|
|
|
{{-- 확정 (draft만) --}}
|
|
@if($payroll->isConfirmable())
|
|
<button type="button" onclick="confirmPayroll({{ $payroll->id }})" class="p-1 text-blue-600 hover:text-blue-800" title="확정">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</button>
|
|
@endif
|
|
|
|
{{-- 확정 취소 (confirmed만) --}}
|
|
@if($payroll->isUnconfirmable())
|
|
<button type="button" onclick="unconfirmPayroll({{ $payroll->id }})" class="p-1 text-orange-600 hover:text-orange-800" title="확정 취소">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/>
|
|
</svg>
|
|
</button>
|
|
@endif
|
|
|
|
{{-- 지급 (confirmed만) --}}
|
|
@if($payroll->isPayable())
|
|
<button type="button" onclick="payPayroll({{ $payroll->id }})" class="p-1 text-emerald-600 hover:text-emerald-800" title="지급처리">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
</svg>
|
|
</button>
|
|
@endif
|
|
|
|
{{-- 상세보기 (paid) --}}
|
|
@if($payroll->status === 'paid')
|
|
<button type="button" onclick="openPayrollDetail({{ $payroll->id }}, {{ json_encode([
|
|
'user_name' => $displayName,
|
|
'department' => $department?->name ?? '-',
|
|
'period' => $payroll->period_label,
|
|
'base_salary' => $payroll->base_salary,
|
|
'overtime_pay' => $payroll->overtime_pay,
|
|
'bonus' => $payroll->bonus,
|
|
'allowances' => $payroll->allowances,
|
|
'gross_salary' => $payroll->gross_salary,
|
|
'income_tax' => $payroll->income_tax,
|
|
'resident_tax' => $payroll->resident_tax,
|
|
'health_insurance' => $payroll->health_insurance,
|
|
'long_term_care' => $payroll->long_term_care,
|
|
'pension' => $payroll->pension,
|
|
'employment_insurance' => $payroll->employment_insurance,
|
|
'deductions' => $payroll->deductions,
|
|
'total_deductions' => $payroll->total_deductions,
|
|
'net_salary' => $payroll->net_salary,
|
|
'confirmed_at' => $payroll->confirmed_at?->format('Y-m-d H:i'),
|
|
'paid_at' => $payroll->paid_at?->format('Y-m-d H:i'),
|
|
'note' => $payroll->note,
|
|
]) }})" class="p-1 text-gray-600 hover:text-gray-800" title="상세보기">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
|
</svg>
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="9" 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="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
</svg>
|
|
<p class="text-gray-500">급여 기록이 없습니다.</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</x-table-swipe>
|
|
|
|
{{-- 페이지네이션 --}}
|
|
@if($payrolls->hasPages())
|
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
|
{{ $payrolls->links() }}
|
|
</div>
|
|
@endif
|