From c40e9e5e877ab79d886b61b5b3f867fcc98170cb Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 14 Jun 2026 13:36:25 +0700 Subject: [PATCH] fix: enforce cash balance validation, add due date constraint, and improve error message feedback in finance modules --- .../Admin/Finance/EmployeeAdvanceRequest.php | 2 +- app/Services/Finance/CashService.php | 24 ++++++++++++++++++- .../admin/finance/cash/data-table-actions.vue | 4 ++-- .../employee-advances/data-table-actions.vue | 8 +++---- .../finance/expenses/data-table-actions.vue | 4 ++-- .../finance/payroll/data-table-actions.vue | 4 ++-- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/app/Http/Requests/Admin/Finance/EmployeeAdvanceRequest.php b/app/Http/Requests/Admin/Finance/EmployeeAdvanceRequest.php index db5925e..e261226 100644 --- a/app/Http/Requests/Admin/Finance/EmployeeAdvanceRequest.php +++ b/app/Http/Requests/Admin/Finance/EmployeeAdvanceRequest.php @@ -44,7 +44,7 @@ public function rules(): array return [ 'amount' => ['required', 'integer', 'min:1'], 'description' => ['required', 'string', 'max:100'], - 'due_date' => ['required', 'date'], + 'due_date' => ['required', 'date','after_or_equal:today'], ...$this->photoRules(), ]; } diff --git a/app/Services/Finance/CashService.php b/app/Services/Finance/CashService.php index 5c89815..48fc810 100644 --- a/app/Services/Finance/CashService.php +++ b/app/Services/Finance/CashService.php @@ -181,6 +181,14 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v $this->syncPhotos($transaction, $validated); $this->recalculateBalances($transaction->cashAccount); + + $account = $transaction->cashAccount->fresh(); + + if ($account->balance < 0) { + throw ValidationException::withMessages([ + 'amount' => 'Saldo kas tidak mencukupi.', + ]); + } }); $this->pushNotificationService->sendToRoles( @@ -198,10 +206,18 @@ public function deleteTransaction(CashTransaction $transaction): void DB::transaction(function () use ($transaction): void { CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); + $account = $transaction->cashAccount; + $transaction->clearMediaCollection('photos'); $transaction->delete(); - $this->recalculateBalances($transaction->cashAccount); + $this->recalculateBalances($account); + + if ($account->fresh()->balance < 0) { + throw ValidationException::withMessages([ + 'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.', + ]); + } }); $this->pushNotificationService->sendToRoles( @@ -245,6 +261,12 @@ public function deleteReferencedTransaction(CashTransaction $transaction): void $transaction->delete(); $this->recalculateBalances($account); + + if ($account->fresh()->balance < 0) { + throw ValidationException::withMessages([ + 'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.', + ]); + } }); } diff --git a/resources/js/components/admin/finance/cash/data-table-actions.vue b/resources/js/components/admin/finance/cash/data-table-actions.vue index 3ccf117..48ca5bd 100644 --- a/resources/js/components/admin/finance/cash/data-table-actions.vue +++ b/resources/js/components/admin/finance/cash/data-table-actions.vue @@ -32,8 +32,8 @@ function destroyTransaction() { onSuccess: () => { deleteConfirmOpen.value = false; }, - onError: () => { - toast.error('Gagal menghapus setor kas.'); + onError: (errors) => { + toast.error(errors.amount || errors.transaction || 'Gagal menghapus setor kas.'); }, onFinish: () => { deleteProcessing.value = false; diff --git a/resources/js/components/admin/finance/employee-advances/data-table-actions.vue b/resources/js/components/admin/finance/employee-advances/data-table-actions.vue index f198fe3..727604b 100644 --- a/resources/js/components/admin/finance/employee-advances/data-table-actions.vue +++ b/resources/js/components/admin/finance/employee-advances/data-table-actions.vue @@ -72,8 +72,8 @@ function approveEmployeeAdvance() { onSuccess: () => { approveConfirmOpen.value = false; }, - onError: () => { - toast.error('Gagal menyetujui kasbon.'); + onError: (errors) => { + toast.error(errors.amount || errors.employee_advance || 'Gagal menyetujui kasbon.'); }, onFinish: () => { approveProcessing.value = false; @@ -89,8 +89,8 @@ function payEmployeeAdvance() { onSuccess: () => { payConfirmOpen.value = false; }, - onError: () => { - toast.error('Gagal melunasi kasbon.'); + onError: (errors) => { + toast.error(errors.amount || errors.employee_advance || 'Gagal melunasi kasbon.'); }, onFinish: () => { payProcessing.value = false; diff --git a/resources/js/components/admin/finance/expenses/data-table-actions.vue b/resources/js/components/admin/finance/expenses/data-table-actions.vue index 795056a..418be15 100644 --- a/resources/js/components/admin/finance/expenses/data-table-actions.vue +++ b/resources/js/components/admin/finance/expenses/data-table-actions.vue @@ -30,8 +30,8 @@ function destroyExpense() { onSuccess: () => { deleteConfirmOpen.value = false; }, - onError: () => { - toast.error('Gagal menghapus pengeluaran.'); + onError: (errors) => { + toast.error(errors.amount || errors.transaction || 'Gagal menghapus pengeluaran.'); }, onFinish: () => { deleteProcessing.value = false; diff --git a/resources/js/components/admin/finance/payroll/data-table-actions.vue b/resources/js/components/admin/finance/payroll/data-table-actions.vue index 52d4844..bd1e0b4 100644 --- a/resources/js/components/admin/finance/payroll/data-table-actions.vue +++ b/resources/js/components/admin/finance/payroll/data-table-actions.vue @@ -30,8 +30,8 @@ function payPayroll() { onSuccess: () => { payConfirmOpen.value = false; }, - onError: () => { - toast.error('Gagal membayar gaji.'); + onError: (errors) => { + toast.error(errors.amount || errors.payroll || 'Gagal membayar gaji.'); }, onFinish: () => { payProcessing.value = false;