with(['createdBy.profile', 'media']) ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void { $search = $tableQuery['search']; $query->where(function (Builder $query) use ($search): void { $query->where('description', 'like', "%{$search}%"); }); }); $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); return $query ->paginate(25) ->withQueryString() ->through(function (Expense $expense) { $expense->setAttribute( 'photos', MediaPresenter::collection($expense, 'photos'), ); return $expense; }); } public function create(array $validated, User $user): void { try { $expense = DB::transaction(function () use ($validated, $user): Expense { $amount = (int) $validated['amount']; $description = $validated['description']; $expense = Expense::create([ 'amount' => $amount, 'description' => $description, 'created_by_id' => $user->id, ]); $cashTransaction = $this->cashService->recordOutgoing( $expense, $amount, $description, $user, ); $expense->update([ 'cash_transaction_id' => $cashTransaction->id, ]); $this->syncPhotos($expense, $validated); return $expense; }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { Log::error('Gagal membuat pengeluaran: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); throw ValidationException::withMessages([ 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', ]); } $this->pushNotificationService->sendToRoles( '💸 Pengeluaran Baru', "Pengeluaran baru sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dicatat oleh {$user->profile?->full_name}.", ['owner', 'developer', 'direktur'], route('admin.finance.expenses.index'), ); } public function update(Expense $expense, array $validated): void { try { DB::transaction(function () use ($expense, $validated): void { $amount = (int) $validated['amount']; $description = $validated['description']; $expense->update([ 'amount' => $amount, 'description' => $description, ]); if ($expense->cashTransaction) { $this->cashService->updateReferencedTransaction( $expense->cashTransaction, $amount, $description, ); } $this->syncPhotos($expense, $validated); }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { Log::error('Gagal memperbarui pengeluaran: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); throw ValidationException::withMessages([ 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', ]); } $this->pushNotificationService->sendToRoles( '✏️ Pengeluaran Diperbarui', "Pengeluaran dengan keterangan {$expense->description} diperbarui menjadi sebesar {$expense->amount_formatted}.", ['owner', 'developer', 'direktur'], route('admin.finance.expenses.index'), ); } public function delete(Expense $expense): void { try { DB::transaction(function () use ($expense): void { if ($expense->cashTransaction) { $this->cashService->deleteReferencedTransaction($expense->cashTransaction); } $expense->clearMediaCollection('photos'); $expense->delete(); }); } catch (ValidationException $e) { throw $e; } catch (\Throwable $e) { Log::error('Gagal menghapus pengeluaran: '.$e->getMessage(), [ 'trace' => $e->getTraceAsString(), ]); throw ValidationException::withMessages([ 'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.', ]); } $this->pushNotificationService->sendToRoles( '🗑️ Pengeluaran Dihapus', "Pengeluaran sebesar {$expense->amount_formatted} dengan keterangan {$expense->description} telah dihapus.", ['owner', 'developer', 'direktur'], route('admin.finance.expenses.index'), ); } private function syncPhotos(Expense $expense, array $validated): void { $this->mediaService->syncCollection( $expense, 'photos', $validated['photos'] ?? null, $validated['remove_media_ids'] ?? null, self::MAX_PHOTOS, required: true, errorKey: 'photos', s3Keys: $validated['s3_keys'] ?? null, ); } private function applySorting(Builder $query, string $sort, string $direction): void { if (in_array($sort, ['created_at', 'amount', 'description'], true)) { $query->orderBy($sort, $direction); return; } $query->latest(); } }