with(['chartOfAccount', 'budgetItem.budget', 'createdBy']); // Filter by transaction type if ($type = $request->string('transaction_type')->toString()) { $query->where('transaction_type', $type); } // Filter by account if ($accountId = $request->integer('chart_of_account_id')) { $query->where('chart_of_account_id', $accountId); } // Filter by budget if ($budgetId = $request->integer('budget_id')) { $query->whereHas('budgetItem', fn($q) => $q->where('budget_id', $budgetId)); } // Filter by date range if ($startDate = $request->date('start_date')) { $query->where('transaction_date', '>=', $startDate); } if ($endDate = $request->date('end_date')) { $query->where('transaction_date', '<=', $endDate); } // Search description if ($search = $request->string('search')->toString()) { $query->where(function ($q) use ($search) { $q->where('description', 'like', "%{$search}%") ->orWhere('reference_number', 'like', "%{$search}%"); }); } $transactions = $query->orderByDesc('transaction_date') ->orderByDesc('created_at') ->paginate(20); // Get filter options $accounts = ChartOfAccount::where('is_active', true) ->whereIn('account_type', ['income', 'expense']) ->orderBy('account_code') ->get(); $budgets = Budget::orderByDesc('fiscal_year')->get(); // Calculate totals $totalIncome = (clone $query)->income()->sum('amount'); $totalExpense = (clone $query)->expense()->sum('amount'); return view('admin.transactions.index', [ 'transactions' => $transactions, 'accounts' => $accounts, 'budgets' => $budgets, 'totalIncome' => $totalIncome, 'totalExpense' => $totalExpense, ]); } public function create(Request $request) { // Get active budgets $budgets = Budget::whereIn('status', [Budget::STATUS_ACTIVE, Budget::STATUS_APPROVED]) ->orderByDesc('fiscal_year') ->get(); // Get income and expense accounts $incomeAccounts = ChartOfAccount::where('account_type', 'income') ->where('is_active', true) ->orderBy('account_code') ->get(); $expenseAccounts = ChartOfAccount::where('account_type', 'expense') ->where('is_active', true) ->orderBy('account_code') ->get(); // Pre-select budget if provided $selectedBudgetId = $request->integer('budget_id'); return view('admin.transactions.create', [ 'budgets' => $budgets, 'incomeAccounts' => $incomeAccounts, 'expenseAccounts' => $expenseAccounts, 'selectedBudgetId' => $selectedBudgetId, ]); } public function store(Request $request) { $validated = $request->validate([ 'chart_of_account_id' => ['required', 'exists:chart_of_accounts,id'], 'transaction_date' => ['required', 'date'], 'amount' => ['required', 'numeric', 'min:0.01'], 'transaction_type' => ['required', 'in:income,expense'], 'description' => ['required', 'string', 'max:255'], 'reference_number' => ['nullable', 'string', 'max:255'], 'budget_item_id' => ['nullable', 'exists:budget_items,id'], 'notes' => ['nullable', 'string'], ]); DB::transaction(function () use ($validated, $request) { $transaction = Transaction::create([ ...$validated, 'created_by_user_id' => $request->user()->id, ]); // Update budget item actual amount if linked if ($transaction->budget_item_id) { $this->updateBudgetItemActual($transaction->budget_item_id); } AuditLogger::log('transaction.created', $transaction, [ 'user' => $request->user()->name, 'amount' => $validated['amount'], 'type' => $validated['transaction_type'], ]); }); return redirect() ->route('admin.transactions.index') ->with('status', __('Transaction recorded successfully.')); } public function show(Transaction $transaction) { $transaction->load([ 'chartOfAccount', 'budgetItem.budget', 'budgetItem.chartOfAccount', 'financeDocument', 'membershipPayment', 'createdBy', ]); return view('admin.transactions.show', [ 'transaction' => $transaction, ]); } public function edit(Transaction $transaction) { // Only allow editing if not linked to finance document or payment if ($transaction->finance_document_id || $transaction->membership_payment_id) { return redirect() ->route('admin.transactions.show', $transaction) ->with('error', __('Cannot edit auto-generated transactions.')); } $budgets = Budget::whereIn('status', [Budget::STATUS_ACTIVE, Budget::STATUS_APPROVED]) ->orderByDesc('fiscal_year') ->get(); $incomeAccounts = ChartOfAccount::where('account_type', 'income') ->where('is_active', true) ->orderBy('account_code') ->get(); $expenseAccounts = ChartOfAccount::where('account_type', 'expense') ->where('is_active', true) ->orderBy('account_code') ->get(); return view('admin.transactions.edit', [ 'transaction' => $transaction, 'budgets' => $budgets, 'incomeAccounts' => $incomeAccounts, 'expenseAccounts' => $expenseAccounts, ]); } public function update(Request $request, Transaction $transaction) { // Only allow editing if not auto-generated if ($transaction->finance_document_id || $transaction->membership_payment_id) { abort(403, 'Cannot edit auto-generated transactions.'); } $validated = $request->validate([ 'chart_of_account_id' => ['required', 'exists:chart_of_accounts,id'], 'transaction_date' => ['required', 'date'], 'amount' => ['required', 'numeric', 'min:0.01'], 'description' => ['required', 'string', 'max:255'], 'reference_number' => ['nullable', 'string', 'max:255'], 'budget_item_id' => ['nullable', 'exists:budget_items,id'], 'notes' => ['nullable', 'string'], ]); DB::transaction(function () use ($transaction, $validated, $request) { $oldBudgetItemId = $transaction->budget_item_id; $transaction->update($validated); // Update budget item actuals if ($oldBudgetItemId) { $this->updateBudgetItemActual($oldBudgetItemId); } if ($transaction->budget_item_id && $transaction->budget_item_id != $oldBudgetItemId) { $this->updateBudgetItemActual($transaction->budget_item_id); } AuditLogger::log('transaction.updated', $transaction, [ 'user' => $request->user()->name, ]); }); return redirect() ->route('admin.transactions.show', $transaction) ->with('status', __('Transaction updated successfully.')); } public function destroy(Request $request, Transaction $transaction) { // Only allow deleting if not auto-generated if ($transaction->finance_document_id || $transaction->membership_payment_id) { abort(403, 'Cannot delete auto-generated transactions.'); } $budgetItemId = $transaction->budget_item_id; DB::transaction(function () use ($transaction, $budgetItemId, $request) { $transaction->delete(); // Update budget item actual if ($budgetItemId) { $this->updateBudgetItemActual($budgetItemId); } AuditLogger::log('transaction.deleted', null, [ 'user' => $request->user()->name, 'description' => $transaction->description, 'amount' => $transaction->amount, ]); }); return redirect() ->route('admin.transactions.index') ->with('status', __('Transaction deleted successfully.')); } /** * Update budget item actual amount based on all transactions */ protected function updateBudgetItemActual(int $budgetItemId): void { $budgetItem = BudgetItem::find($budgetItemId); if (!$budgetItem) { return; } $actualAmount = Transaction::where('budget_item_id', $budgetItemId) ->sum('amount'); $budgetItem->update(['actual_amount' => $actualAmount]); } }