diff --git a/app/Http/Controllers/Api/Admin/HR/PayrollController.php b/app/Http/Controllers/Api/Admin/HR/PayrollController.php index 51aac929..123eedfc 100644 --- a/app/Http/Controllers/Api/Admin/HR/PayrollController.php +++ b/app/Http/Controllers/Api/Admin/HR/PayrollController.php @@ -278,6 +278,41 @@ public function confirm(Request $request, int $id): JsonResponse } } + /** + * 급여 확정 취소 + */ + public function unconfirm(Request $request, int $id): JsonResponse + { + if ($denied = $this->checkPayrollAccess()) { + return $denied; + } + + try { + $payroll = $this->payrollService->unconfirmPayroll($id); + + if (! $payroll) { + return response()->json([ + 'success' => false, + 'message' => '급여 확정을 취소할 수 없습니다.', + ], 404); + } + + return response()->json([ + 'success' => true, + 'message' => '급여 확정이 취소되었습니다.', + 'data' => $payroll, + ]); + } catch (\Throwable $e) { + report($e); + + return response()->json([ + 'success' => false, + 'message' => '급여 확정 취소 중 오류가 발생했습니다.', + 'error' => config('app.debug') ? $e->getMessage() : null, + ], 500); + } + } + /** * 급여 지급 처리 */ diff --git a/app/Models/HR/Payroll.php b/app/Models/HR/Payroll.php index 59f2445f..78ca3359 100644 --- a/app/Models/HR/Payroll.php +++ b/app/Models/HR/Payroll.php @@ -141,6 +141,11 @@ public function isConfirmable(): bool return $this->status === self::STATUS_DRAFT; } + public function isUnconfirmable(): bool + { + return $this->status === self::STATUS_CONFIRMED; + } + public function isPayable(): bool { return $this->status === self::STATUS_CONFIRMED; diff --git a/app/Services/HR/PayrollService.php b/app/Services/HR/PayrollService.php index 67c04d5a..813179d0 100644 --- a/app/Services/HR/PayrollService.php +++ b/app/Services/HR/PayrollService.php @@ -278,6 +278,31 @@ public function confirmPayroll(int $id): ?Payroll return $payroll->fresh(['user']); } + /** + * 급여 확정 취소 (confirmed → draft) + */ + public function unconfirmPayroll(int $id): ?Payroll + { + $tenantId = session('selected_tenant_id', 1); + + $payroll = Payroll::query() + ->forTenant($tenantId) + ->find($id); + + if (! $payroll || ! $payroll->isUnconfirmable()) { + return null; + } + + $payroll->update([ + 'status' => Payroll::STATUS_DRAFT, + 'confirmed_at' => null, + 'confirmed_by' => null, + 'updated_by' => auth()->id(), + ]); + + return $payroll->fresh(['user']); + } + /** * 급여 지급 처리 */ diff --git a/resources/views/hr/payrolls/index.blade.php b/resources/views/hr/payrolls/index.blade.php index c71aa30c..2c5b44b9 100644 --- a/resources/views/hr/payrolls/index.blade.php +++ b/resources/views/hr/payrolls/index.blade.php @@ -888,6 +888,33 @@ function confirmPayroll(id) { }); } + // ===== 급여 확정 취소 ===== + function unconfirmPayroll(id) { + if (!confirm('이 급여의 확정을 취소하시겠습니까? 작성중 상태로 되돌아갑니다.')) return; + + fetch('{{ url("/api/admin/hr/payrolls") }}/' + id + '/unconfirm', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}', + 'Accept': 'application/json', + }, + }) + .then(r => r.json()) + .then(result => { + if (result.success) { + refreshTable(); + refreshStats(); + showToast(result.message, 'success'); + } else { + showToast(result.message, 'error'); + } + }) + .catch(err => { + console.error(err); + showToast('확정 취소 중 오류가 발생했습니다.', 'error'); + }); + } + // ===== 급여 지급 ===== function payPayroll(id) { if (!confirm('이 급여를 지급 처리하시겠습니까?')) return; diff --git a/resources/views/hr/payrolls/partials/table.blade.php b/resources/views/hr/payrolls/partials/table.blade.php index a96f3165..a75b4d92 100644 --- a/resources/views/hr/payrolls/partials/table.blade.php +++ b/resources/views/hr/payrolls/partials/table.blade.php @@ -121,6 +121,15 @@ @endif + {{-- 확정 취소 (confirmed만) --}} + @if($payroll->isUnconfirmable()) + + @endif + {{-- 지급 (confirmed만) --}} @if($payroll->isPayable())