Initial commit
This commit is contained in:
272
app/Http/Controllers/TransactionController.php
Normal file
272
app/Http/Controllers/TransactionController.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Budget;
|
||||
use App\Models\BudgetItem;
|
||||
use App\Models\ChartOfAccount;
|
||||
use App\Models\Transaction;
|
||||
use App\Support\AuditLogger;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TransactionController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Transaction::query()
|
||||
->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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user