From db58fbe0498ac6f51a68f98589ec51907d448072 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 29 Jul 2026 20:25:43 +0700 Subject: [PATCH] feat: add update and delete functionality for cash transactions in CashAccountController and CashAccountService --- .../Admin/Finance/CashAccountController.php | 19 +++++ .../Admin/Finance/CashAccountService.php | 59 +++++++++++++ app/Services/Admin/Finance/ExpenseService.php | 2 - .../admin/finance/cash-account/index.tsx | 85 ++++++++++++++++++- .../cash-account/transaction-columns.tsx | 71 +++++++++++++++- routes/web.php | 2 + 6 files changed, 232 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Admin/Finance/CashAccountController.php b/app/Http/Controllers/Admin/Finance/CashAccountController.php index 7c59f3b..07731a6 100644 --- a/app/Http/Controllers/Admin/Finance/CashAccountController.php +++ b/app/Http/Controllers/Admin/Finance/CashAccountController.php @@ -4,6 +4,7 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Admin\Finance\CashTransactionRequest; +use App\Models\CashTransaction; use App\Services\Admin\Finance\CashAccountService; use Illuminate\Http\RedirectResponse; use Inertia\Inertia; @@ -42,4 +43,22 @@ public function withdrawal(CashTransactionRequest $request): RedirectResponse return to_route('admin.finance.cash-accounts.index'); } + + public function update(CashTransactionRequest $request, CashTransaction $transaction): RedirectResponse + { + $this->service->updateTransaction($transaction, $request->validated()); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Transaksi berhasil diperbarui.']); + + return to_route('admin.finance.cash-accounts.index'); + } + + public function destroy(CashTransaction $transaction): RedirectResponse + { + $this->service->deleteTransaction($transaction); + + Inertia::flash('toast', ['type' => 'success', 'message' => 'Transaksi berhasil dihapus.']); + + return to_route('admin.finance.cash-accounts.index'); + } } diff --git a/app/Services/Admin/Finance/CashAccountService.php b/app/Services/Admin/Finance/CashAccountService.php index 67447df..cd6e2e9 100644 --- a/app/Services/Admin/Finance/CashAccountService.php +++ b/app/Services/Admin/Finance/CashAccountService.php @@ -68,4 +68,63 @@ public function withdrawal(array $data): CashTransaction 'description' => $data['description'], ]); } + + public function updateTransaction(CashTransaction $transaction, array $data): CashTransaction + { + if (! in_array($transaction->type, [CashTransactionType::DEPOSIT, CashTransactionType::WITHDRAWAL])) { + 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'], + ]); + + return $transaction; + } + + public function deleteTransaction(CashTransaction $transaction): bool + { + if (! in_array($transaction->type, [CashTransactionType::DEPOSIT, CashTransactionType::WITHDRAWAL])) { + throw ValidationException::withMessages([ + 'amount' => 'Transaksi ini tidak dapat dihapus.', + ]); + } + + $cashAccount = CashAccount::firstOrFail(); + $isDeposit = $transaction->type === CashTransactionType::DEPOSIT; + + $newBalance = $isDeposit + ? $cashAccount->balance - $transaction->amount + : $cashAccount->balance + $transaction->amount; + + if ($newBalance < 0) { + throw ValidationException::withMessages([ + 'amount' => 'Saldo tidak mencukupi untuk menghapus transaksi ini.', + ]); + } + + $cashAccount->update(['balance' => $newBalance]); + + return $transaction->delete(); + } } diff --git a/app/Services/Admin/Finance/ExpenseService.php b/app/Services/Admin/Finance/ExpenseService.php index 0d352ad..7d78406 100644 --- a/app/Services/Admin/Finance/ExpenseService.php +++ b/app/Services/Admin/Finance/ExpenseService.php @@ -94,8 +94,6 @@ public function delete(Expense $expense): bool $newBalance = $cashAccount->balance + $expense->amount; $cashAccount->update(['balance' => $newBalance]); - $cashTransaction->delete(); - return $expense->delete(); }); } diff --git a/resources/js/pages/admin/finance/cash-account/index.tsx b/resources/js/pages/admin/finance/cash-account/index.tsx index 0438d3f..a8c7fc6 100644 --- a/resources/js/pages/admin/finance/cash-account/index.tsx +++ b/resources/js/pages/admin/finance/cash-account/index.tsx @@ -1,4 +1,4 @@ -import { Form, Head } from '@inertiajs/react'; +import { Form, Head, router } from '@inertiajs/react'; import { ArrowDownToLine, ArrowUpFromLine, Wallet } from 'lucide-react'; import { useState } from 'react'; import InputError from '@/components/input-error'; @@ -16,9 +16,11 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { formatCurrency } from '@/lib/utils'; import { index as cashAccountIndex, deposit, withdrawal } from '@/routes/admin/finance/cash-accounts'; +import { update as updateTransaction, destroy as destroyTransaction } from '@/routes/admin/finance/cash-accounts/transactions'; import { createTransactionColumns } from './transaction-columns'; import type { CashTransaction } from './transaction-columns'; import { DataTable } from '@/components/data-table'; +import { ConfirmDialog } from '@/components/confirm-dialog'; type CashAccount = { id: number; @@ -34,8 +36,23 @@ type Props = { export default function CashAccountIndex({ cashAccount, transactions }: Props) { const [depositOpen, setDepositOpen] = useState(false); const [withdrawalOpen, setWithdrawalOpen] = useState(false); + const [editing, setEditing] = useState(null); + const [deleting, setDeleting] = useState(null); - const columns = createTransactionColumns(); + function handleDelete() { + if (!deleting) { + return; + } + + router.delete(destroyTransaction(deleting.id), { + onSuccess: () => setDeleting(null), + }); + } + + const columns = createTransactionColumns({ + handleEdit: (transaction) => setEditing(transaction), + handleDeleteClick: (transaction) => setDeleting(transaction), + }); return ( <> @@ -163,6 +180,70 @@ export default function CashAccountIndex({ cashAccount, transactions }: Props) { + + { + if (!open) { + setEditing(null); + } + }} + > + + {editing && ( +
setEditing(null)}> + {({ errors, processing }) => ( + <> + + Edit Transaksi + +
+
+ + + +
+
+ + + +
+
+ + + + + + )} +
+ )} +
+
+ + { + if (!open) { + setDeleting(null); + } + }} + title="Hapus Transaksi" + description={`Apakah Anda yakin ingin menghapus transaksi "${deleting?.description}"? Tindakan ini tidak dapat dibatalkan.`} + confirmLabel="Hapus" + onConfirm={handleDelete} + /> ); diff --git a/resources/js/pages/admin/finance/cash-account/transaction-columns.tsx b/resources/js/pages/admin/finance/cash-account/transaction-columns.tsx index 573daeb..550bcf6 100644 --- a/resources/js/pages/admin/finance/cash-account/transaction-columns.tsx +++ b/resources/js/pages/admin/finance/cash-account/transaction-columns.tsx @@ -1,7 +1,13 @@ import { Button } from '@/components/ui/button'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; import { formatCurrency } from '@/lib/utils'; import type { ColumnDef } from '@tanstack/react-table'; -import { ArrowUpDown } from 'lucide-react'; +import { ArrowUpDown, Pencil, Trash2 } from 'lucide-react'; export type CashTransaction = { id: number; @@ -55,7 +61,16 @@ function getReferenceLabel(type: string): string { return labels[type] ?? '-'; } -export function createTransactionColumns(): ColumnDef[] { +type CreateColumnsParams = { + handleEdit: (transaction: CashTransaction) => void; + handleDeleteClick: (transaction: CashTransaction) => void; +}; + +export function createTransactionColumns( + params: CreateColumnsParams, +): ColumnDef[] { + const { handleEdit, handleDeleteClick } = params; + return [ { id: 'no', @@ -167,5 +182,57 @@ export function createTransactionColumns(): ColumnDef[] { return {createdBy?.user_profile?.full_name ?? '-'}; }, }, + { + id: 'actions', + header: () => Aksi, + meta: { + className: 'w-[100px] text-center', + headerClassName: 'w-[100px] text-center', + }, + cell: ({ row }) => { + const transaction = row.original; + const canEdit = transaction.type === 'deposit' || transaction.type === 'withdrawal'; + + if (!canEdit) { + return -; + } + + return ( + +
+ + + + + + Edit + + + + + + + + + Hapus + + +
+
+ ); + }, + }, ]; } diff --git a/routes/web.php b/routes/web.php index d5c751c..f0def91 100644 --- a/routes/web.php +++ b/routes/web.php @@ -22,6 +22,8 @@ Route::get('cash-accounts', [CashAccountController::class, 'index'])->name('cash-accounts.index'); Route::post('cash-accounts/deposit', [CashAccountController::class, 'deposit'])->name('cash-accounts.deposit'); Route::post('cash-accounts/withdrawal', [CashAccountController::class, 'withdrawal'])->name('cash-accounts.withdrawal'); + Route::put('cash-accounts/transactions/{transaction}', [CashAccountController::class, 'update'])->name('cash-accounts.transactions.update'); + Route::delete('cash-accounts/transactions/{transaction}', [CashAccountController::class, 'destroy'])->name('cash-accounts.transactions.destroy'); Route::resource('expenses', ExpenseController::class)->except(['show', 'create', 'edit']); });