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())