feat: enhance transaction handling with error management and database transactions in CashAccountService and controllers

This commit is contained in:
Yoga Pangestu 2026-07-29 21:07:30 +07:00
parent 912f576c74
commit d1f1d82099
3 changed files with 143 additions and 86 deletions

View File

@ -7,6 +7,7 @@
use App\Models\CashTransaction; use App\Models\CashTransaction;
use App\Services\Admin\Finance\CashAccountService; use App\Services\Admin\Finance\CashAccountService;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
@ -28,36 +29,64 @@ public function index(): Response
public function deposit(CashTransactionRequest $request): RedirectResponse public function deposit(CashTransactionRequest $request): RedirectResponse
{ {
$this->service->deposit($request->validated()); try {
$this->service->deposit($request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Deposit berhasil ditambahkan.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Deposit berhasil ditambahkan.']);
} catch (ValidationException $e) {
$firstError = collect($e->errors())->flatten()->first();
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.cash-accounts.index'); return to_route('admin.finance.cash-accounts.index');
} }
public function withdrawal(CashTransactionRequest $request): RedirectResponse public function withdrawal(CashTransactionRequest $request): RedirectResponse
{ {
$this->service->withdrawal($request->validated()); try {
$this->service->withdrawal($request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Withdrawal berhasil ditambahkan.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Withdrawal berhasil ditambahkan.']);
} catch (ValidationException $e) {
$firstError = collect($e->errors())->flatten()->first();
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.cash-accounts.index'); return to_route('admin.finance.cash-accounts.index');
} }
public function update(CashTransactionRequest $request, CashTransaction $transaction): RedirectResponse public function update(CashTransactionRequest $request, CashTransaction $transaction): RedirectResponse
{ {
$this->service->updateTransaction($transaction, $request->validated()); try {
$this->service->updateTransaction($transaction, $request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Transaksi berhasil diperbarui.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Transaksi berhasil diperbarui.']);
} catch (ValidationException $e) {
$firstError = collect($e->errors())->flatten()->first();
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.cash-accounts.index'); return to_route('admin.finance.cash-accounts.index');
} }
public function destroy(CashTransaction $transaction): RedirectResponse public function destroy(CashTransaction $transaction): RedirectResponse
{ {
$this->service->deleteTransaction($transaction); try {
$this->service->deleteTransaction($transaction);
Inertia::flash('toast', ['type' => 'success', 'message' => 'Transaksi berhasil dihapus.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Transaksi berhasil dihapus.']);
} catch (ValidationException $e) {
$firstError = collect($e->errors())->flatten()->first();
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.cash-accounts.index'); return to_route('admin.finance.cash-accounts.index');
} }

View File

@ -7,6 +7,7 @@
use App\Models\Expense; use App\Models\Expense;
use App\Services\Admin\Finance\ExpenseService; use App\Services\Admin\Finance\ExpenseService;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
@ -25,27 +26,45 @@ public function index(): Response
public function store(ExpenseRequest $request): RedirectResponse public function store(ExpenseRequest $request): RedirectResponse
{ {
$this->service->create($request->validated()); try {
$this->service->create($request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Pengeluaran berhasil ditambahkan.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Pengeluaran berhasil ditambahkan.']);
} catch (ValidationException $e) {
$firstError = collect($e->errors())->flatten()->first();
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.expenses.index'); return to_route('admin.finance.expenses.index');
} }
public function update(ExpenseRequest $request, Expense $expense): RedirectResponse public function update(ExpenseRequest $request, Expense $expense): RedirectResponse
{ {
$this->service->update($expense, $request->validated()); try {
$this->service->update($expense, $request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Pengeluaran berhasil diperbarui.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Pengeluaran berhasil diperbarui.']);
} catch (ValidationException $e) {
$firstError = collect($e->errors())->flatten()->first();
Inertia::flash('toast', ['type' => 'error', 'message' => $firstError ?? 'Terjadi kesalahan.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.expenses.index'); return to_route('admin.finance.expenses.index');
} }
public function destroy(Expense $expense): RedirectResponse public function destroy(Expense $expense): RedirectResponse
{ {
$this->service->delete($expense); try {
$this->service->delete($expense);
Inertia::flash('toast', ['type' => 'success', 'message' => 'Pengeluaran berhasil dihapus.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Pengeluaran berhasil dihapus.']);
} catch (\Exception $e) {
Inertia::flash('toast', ['type' => 'error', 'message' => $e->getMessage()]);
}
return to_route('admin.finance.expenses.index'); return to_route('admin.finance.expenses.index');
} }

View File

@ -6,6 +6,7 @@
use App\Models\CashAccount; use App\Models\CashAccount;
use App\Models\CashTransaction; use App\Models\CashTransaction;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
class CashAccountService class CashAccountService
@ -31,100 +32,108 @@ public function getAllTransactions(): Collection
public function deposit(array $data): CashTransaction public function deposit(array $data): CashTransaction
{ {
$cashAccount = CashAccount::firstOrFail(); return DB::transaction(function () use ($data) {
$newBalance = $cashAccount->balance + $data['amount']; $cashAccount = CashAccount::firstOrFail();
$newBalance = $cashAccount->balance + $data['amount'];
$cashAccount->update(['balance' => $newBalance]); $cashAccount->update(['balance' => $newBalance]);
return CashTransaction::create([ return CashTransaction::create([
'cash_account_id' => $cashAccount->id, 'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(), 'created_by_id' => auth()->id(),
'amount' => $data['amount'], 'amount' => $data['amount'],
'balance_after' => $newBalance, 'balance_after' => $newBalance,
'type' => CashTransactionType::DEPOSIT, 'type' => CashTransactionType::DEPOSIT,
'description' => $data['description'], 'description' => $data['description'],
]); ]);
});
} }
public function withdrawal(array $data): CashTransaction public function withdrawal(array $data): CashTransaction
{ {
$cashAccount = CashAccount::firstOrFail(); return DB::transaction(function () use ($data) {
$cashAccount = CashAccount::firstOrFail();
if ($cashAccount->balance < $data['amount']) { if ($cashAccount->balance < $data['amount']) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.', 'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance - $data['amount'];
$cashAccount->update(['balance' => $newBalance]);
return CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $data['amount'],
'balance_after' => $newBalance,
'type' => CashTransactionType::WITHDRAWAL,
'description' => $data['description'],
]); ]);
} });
$newBalance = $cashAccount->balance - $data['amount'];
$cashAccount->update(['balance' => $newBalance]);
return CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $data['amount'],
'balance_after' => $newBalance,
'type' => CashTransactionType::WITHDRAWAL,
'description' => $data['description'],
]);
} }
public function updateTransaction(CashTransaction $transaction, array $data): CashTransaction public function updateTransaction(CashTransaction $transaction, array $data): CashTransaction
{ {
if (! in_array($transaction->type, [CashTransactionType::DEPOSIT, CashTransactionType::WITHDRAWAL])) { return DB::transaction(function () use ($transaction, $data) {
throw ValidationException::withMessages([ if (! in_array($transaction->type, [CashTransactionType::DEPOSIT, CashTransactionType::WITHDRAWAL])) {
'amount' => 'Transaksi ini tidak dapat diedit.', throw ValidationException::withMessages([
'amount' => 'Transaksi ini tidak dapat diedit.',
]);
}
$cashAccount = CashAccount::firstOrFail();
$oldAmount = $transaction->amount;
$newAmount = $data['amount'];
$isDeposit = $transaction->type === CashTransactionType::DEPOSIT;
$difference = $isDeposit ? $newAmount - $oldAmount : $oldAmount - $newAmount;
if ($difference < 0 && $cashAccount->balance < abs($difference)) {
throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance + $difference;
$cashAccount->update(['balance' => $newBalance]);
$transaction->update([
'amount' => $newAmount,
'balance_after' => $newBalance,
'description' => $data['description'],
]); ]);
}
$cashAccount = CashAccount::firstOrFail(); return $transaction;
$oldAmount = $transaction->amount; });
$newAmount = $data['amount'];
$isDeposit = $transaction->type === CashTransactionType::DEPOSIT;
$difference = $isDeposit ? $newAmount - $oldAmount : $oldAmount - $newAmount;
if ($difference < 0 && $cashAccount->balance < abs($difference)) {
throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi.',
]);
}
$newBalance = $cashAccount->balance + $difference;
$cashAccount->update(['balance' => $newBalance]);
$transaction->update([
'amount' => $newAmount,
'balance_after' => $newBalance,
'description' => $data['description'],
]);
return $transaction;
} }
public function deleteTransaction(CashTransaction $transaction): bool public function deleteTransaction(CashTransaction $transaction): bool
{ {
if (! in_array($transaction->type, [CashTransactionType::DEPOSIT, CashTransactionType::WITHDRAWAL])) { return DB::transaction(function () use ($transaction) {
throw ValidationException::withMessages([ if (! in_array($transaction->type, [CashTransactionType::DEPOSIT, CashTransactionType::WITHDRAWAL])) {
'amount' => 'Transaksi ini tidak dapat dihapus.', throw ValidationException::withMessages([
]); 'amount' => 'Transaksi ini tidak dapat dihapus.',
} ]);
}
$cashAccount = CashAccount::firstOrFail(); $cashAccount = CashAccount::firstOrFail();
$isDeposit = $transaction->type === CashTransactionType::DEPOSIT; $isDeposit = $transaction->type === CashTransactionType::DEPOSIT;
$newBalance = $isDeposit $newBalance = $isDeposit
? $cashAccount->balance - $transaction->amount ? $cashAccount->balance - $transaction->amount
: $cashAccount->balance + $transaction->amount; : $cashAccount->balance + $transaction->amount;
if ($newBalance < 0) { if ($newBalance < 0) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'amount' => 'Saldo tidak mencukupi untuk menghapus transaksi ini.', 'amount' => 'Saldo tidak mencukupi untuk menghapus transaksi ini.',
]); ]);
} }
$cashAccount->update(['balance' => $newBalance]); $cashAccount->update(['balance' => $newBalance]);
return $transaction->delete(); return $transaction->delete();
});
} }
} }