diff --git a/app/Http/Controllers/Admin/Finance/CashController.php b/app/Http/Controllers/Admin/Finance/CashController.php index 6fee9d8..26817a7 100644 --- a/app/Http/Controllers/Admin/Finance/CashController.php +++ b/app/Http/Controllers/Admin/Finance/CashController.php @@ -5,13 +5,14 @@ use App\Http\Controllers\Concerns\FlashesEntityMessage; use App\Http\Controllers\Concerns\ParsesDataTableQuery; use App\Http\Controllers\Controller; +use App\Http\Requests\Admin\Finance\Cash\CashTransactionRequest; use App\Http\Requests\Admin\Finance\Cash\DepositRequest; use App\Http\Requests\Admin\Finance\Cash\WithdrawRequest; -use App\Http\Requests\Admin\Finance\UpdateCashTransactionRequest; use App\Models\CashTransaction; use App\Services\Finance\CashService; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; @@ -38,8 +39,14 @@ public function index(Request $request): Response ]); } - public function update(UpdateCashTransactionRequest $request, CashTransaction $cashTransaction): RedirectResponse + public function update(CashTransactionRequest $request, CashTransaction $cashTransaction): RedirectResponse { + try { + $cashTransaction->ensureEditable(); + } catch (ValidationException) { + abort(403); + } + $this->cashService->updateDeposit($cashTransaction, $request->validated()); $this->flashUpdated('Setor kas'); @@ -49,6 +56,12 @@ public function update(UpdateCashTransactionRequest $request, CashTransaction $c public function destroy(CashTransaction $cashTransaction): RedirectResponse { + try { + $cashTransaction->ensureEditable(); + } catch (ValidationException) { + abort(403); + } + $this->cashService->deleteTransaction($cashTransaction); $this->flashDeleted('Setor kas'); diff --git a/app/Http/Requests/Admin/Finance/UpdateCashTransactionRequest.php b/app/Http/Requests/Admin/Finance/Cash/CashTransactionRequest.php similarity index 90% rename from app/Http/Requests/Admin/Finance/UpdateCashTransactionRequest.php rename to app/Http/Requests/Admin/Finance/Cash/CashTransactionRequest.php index 5708b3d..5e8742d 100644 --- a/app/Http/Requests/Admin/Finance/UpdateCashTransactionRequest.php +++ b/app/Http/Requests/Admin/Finance/Cash/CashTransactionRequest.php @@ -1,12 +1,12 @@ reference_type !== null) { + throw ValidationException::withMessages([ + 'transaction' => 'Transaksi ini tidak dapat diubah dari halaman kas.', + ]); + } + } + public function getEmployeeAdvanceLabel(): string { return $this->isEmployeeAdvanceRepayment() ? 'Pelunasan Kasbon' : 'Pencairan Kasbon'; diff --git a/app/Services/Finance/CashService.php b/app/Services/Finance/CashService.php index 0842b19..4f83395 100644 --- a/app/Services/Finance/CashService.php +++ b/app/Services/Finance/CashService.php @@ -6,18 +6,20 @@ use App\Models\CashAccount; use App\Models\CashTransaction; use App\Models\User; +use App\Services\Concerns\RunsInTransaction; +use App\Services\Concerns\SyncsPhotos; use App\Services\Media\MediaService; use App\Services\System\PushNotificationService; use App\Support\Media\MediaPresenter; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; class CashService { + use RunsInTransaction, SyncsPhotos; + private const MAX_PHOTOS = 1; public function __construct( @@ -66,8 +68,8 @@ public function paginateForIndex(CashAccount $cashAccount, array $tableQuery, st public function deposit(CashAccount $cashAccount, array $validated, User $user): CashTransaction { - try { - $transaction = DB::transaction(function () use ($cashAccount, $validated, $user): CashTransaction { + $transaction = $this->runInTransaction( + function () use ($cashAccount, $validated, $user): CashTransaction { $account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id); $amount = (int) $validated['amount']; $newBalance = $account->balance + $amount; @@ -83,21 +85,12 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user): 'created_by_id' => $user->id, ]); - $this->syncPhotos($transaction, $validated); + $this->syncPhotos($transaction, $validated, self::MAX_PHOTOS); return $transaction; - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal melakukan setoran kas: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal melakukan setoran kas', + ); $this->pushNotificationService->sendToRoles( '💰 Setoran Kas', @@ -111,8 +104,8 @@ public function deposit(CashAccount $cashAccount, array $validated, User $user): public function withdraw(CashAccount $cashAccount, array $validated, User $user): CashTransaction { - try { - $transaction = DB::transaction(function () use ($cashAccount, $validated, $user): CashTransaction { + $transaction = $this->runInTransaction( + function () use ($cashAccount, $validated, $user): CashTransaction { $account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id); $amount = (int) $validated['amount']; @@ -135,21 +128,12 @@ public function withdraw(CashAccount $cashAccount, array $validated, User $user) 'created_by_id' => $user->id, ]); - $this->syncPhotos($transaction, $validated); + $this->syncPhotos($transaction, $validated, self::MAX_PHOTOS); return $transaction; - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal melakukan tarik kas: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal melakukan tarik kas', + ); $this->pushNotificationService->sendToRoles( '🏦 Tarik Kas', @@ -168,8 +152,8 @@ public function recordOutgoing( User $user, ?CashAccount $cashAccount = null, ): CashTransaction { - try { - return DB::transaction(function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction { + return $this->runInTransaction( + function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction { $account = CashAccount::query()->lockForUpdate()->findOrFail( ($cashAccount ?? $this->getDefaultAccount())->id, ); @@ -197,18 +181,9 @@ public function recordOutgoing( $transaction->save(); return $transaction; - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal mencatat transaksi keluar kas: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal mencatat transaksi keluar kas', + ); } public function recordIncoming( @@ -218,8 +193,8 @@ public function recordIncoming( User $user, ?CashAccount $cashAccount = null, ): CashTransaction { - try { - return DB::transaction(function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction { + return $this->runInTransaction( + function () use ($reference, $amount, $description, $user, $cashAccount): CashTransaction { $account = CashAccount::query()->lockForUpdate()->findOrFail( ($cashAccount ?? $this->getDefaultAccount())->id, ); @@ -241,26 +216,15 @@ public function recordIncoming( $transaction->save(); return $transaction; - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal mencatat transaksi masuk kas: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal mencatat transaksi masuk kas', + ); } public function updateDeposit(CashTransaction $transaction, array $validated): void { - $this->ensureEditable($transaction); - - try { - DB::transaction(function () use ($transaction, $validated): void { + $this->runInTransaction( + function () use ($transaction, $validated): void { CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); $transaction->update([ @@ -268,7 +232,7 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v 'description' => $validated['description'], ]); - $this->syncPhotos($transaction, $validated); + $this->syncPhotos($transaction, $validated, self::MAX_PHOTOS); $this->recalculateBalances($transaction->cashAccount); @@ -279,18 +243,9 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v 'amount' => 'Saldo kas tidak mencukupi.', ]); } - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal memperbarui setoran kas: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal memperbarui setoran kas', + ); $this->pushNotificationService->sendToRoles( '✏️ Transaksi Kas Diperbarui', @@ -302,10 +257,11 @@ public function updateDeposit(CashTransaction $transaction, array $validated): v public function deleteTransaction(CashTransaction $transaction): void { - $this->ensureEditable($transaction); + $amountFormatted = $transaction->amount_formatted; + $description = $transaction->description; - try { - DB::transaction(function () use ($transaction): void { + $this->runInTransaction( + function () use ($transaction): void { CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); $account = $transaction->cashAccount; @@ -320,22 +276,13 @@ public function deleteTransaction(CashTransaction $transaction): void 'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.', ]); } - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal menghapus transaksi kas: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal menghapus transaksi kas', + ); $this->pushNotificationService->sendToRoles( '🗑️ Transaksi Kas Dihapus', - "Transaksi kas senilai {$transaction->amount_formatted} dengan keterangan {$transaction->description} telah dihapus.", + "Transaksi kas senilai {$amountFormatted} dengan keterangan {$description} telah dihapus.", ['owner', 'developer', 'admin-toko'], route('admin.finance.cash.index'), ); @@ -346,8 +293,8 @@ public function updateReferencedTransaction( int $amount, string $description, ): void { - try { - DB::transaction(function () use ($transaction, $amount, $description): void { + $this->runInTransaction( + function () use ($transaction, $amount, $description): void { CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); $transaction->update([ @@ -364,24 +311,15 @@ public function updateReferencedTransaction( 'amount' => 'Saldo kas tidak mencukupi.', ]); } - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal memperbarui transaksi kas referensi: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } + }, + 'Gagal memperbarui transaksi kas referensi', + ); } public function deleteReferencedTransaction(CashTransaction $transaction): void { - try { - DB::transaction(function () use ($transaction): void { + $this->runInTransaction( + function () use ($transaction): void { CashAccount::query()->lockForUpdate()->findOrFail($transaction->cash_account_id); $account = $transaction->cashAccount; @@ -394,43 +332,11 @@ public function deleteReferencedTransaction(CashTransaction $transaction): void 'transaction' => 'Saldo kas tidak mencukupi jika transaksi ini dihapus.', ]); } - }); - } catch (ValidationException $e) { - throw $e; - } catch (\Throwable $e) { - Log::error('Gagal menghapus transaksi kas referensi: '.$e->getMessage(), [ - 'trace' => $e->getTraceAsString(), - ]); - - throw ValidationException::withMessages([ - 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', - ]); - } - } - - private function syncPhotos(CashTransaction $transaction, array $validated): void - { - $this->mediaService->syncCollection( - $transaction, - 'photos', - $validated['photos'] ?? null, - $validated['remove_media_ids'] ?? null, - self::MAX_PHOTOS, - required: true, - errorKey: 'photos', - s3Keys: $validated['s3_keys'] ?? null, + }, + 'Gagal menghapus transaksi kas referensi', ); } - private function ensureEditable(CashTransaction $transaction): void - { - if ($transaction->reference_type !== null) { - throw ValidationException::withMessages([ - 'transaction' => 'Transaksi ini tidak dapat diubah dari halaman kas.', - ]); - } - } - private function recalculateBalances(CashAccount $cashAccount): void { $account = CashAccount::query()->lockForUpdate()->findOrFail($cashAccount->id); diff --git a/resources/js/pages/admin/finance/cash/Index.vue b/resources/js/pages/admin/finance/cash/Index.vue index 9c2460d..68ea961 100644 --- a/resources/js/pages/admin/finance/cash/Index.vue +++ b/resources/js/pages/admin/finance/cash/Index.vue @@ -6,15 +6,19 @@ import { DataTable } from '@/components/data-table'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useCan } from '@/composables/useCan'; +import { + useDataTableQuery, + useDataTableQuerySync, +} from '@/composables/useDataTableQuery'; import { CashReferenceType } from '@/constants/cash-reference-type'; import { CashTransactionType } from '@/constants/cash-transaction-type'; -import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery'; import AdminLayout from '@/layouts/AdminLayout.vue'; +import { index } from '@/routes/admin/finance/cash'; import type { CashAccount, CashTransactionListItem, PaginatedCashTransactions } from '@/types/cash'; import type { DataTableFilterDef, DataTableSort } from '@/types/data-table'; import CashTransactionFormModal from './form/CashTransactionFormModal.vue'; import { createColumns } from './table/columns'; -import { index } from '@/routes/admin/finance/cash'; + const props = defineProps<{ cashAccount: CashAccount; @@ -116,11 +120,13 @@ watch(