270 lines
8.9 KiB
PHP
270 lines
8.9 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Budget;
|
|
use App\Models\BudgetItem;
|
|
use App\Models\ChartOfAccount;
|
|
use App\Support\AuditLogger;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class BudgetController extends Controller
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$query = Budget::query()->with('createdBy', 'approvedBy');
|
|
|
|
// Filter by fiscal year
|
|
if ($fiscalYear = $request->integer('fiscal_year')) {
|
|
$query->where('fiscal_year', $fiscalYear);
|
|
}
|
|
|
|
// Filter by status
|
|
if ($status = $request->string('status')->toString()) {
|
|
$query->where('status', $status);
|
|
}
|
|
|
|
$budgets = $query->orderByDesc('fiscal_year')
|
|
->orderByDesc('created_at')
|
|
->paginate(15);
|
|
|
|
// Get unique fiscal years for filter dropdown
|
|
$fiscalYears = Budget::select('fiscal_year')
|
|
->distinct()
|
|
->orderByDesc('fiscal_year')
|
|
->pluck('fiscal_year');
|
|
|
|
return view('admin.budgets.index', [
|
|
'budgets' => $budgets,
|
|
'fiscalYears' => $fiscalYears,
|
|
]);
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
return view('admin.budgets.create');
|
|
}
|
|
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'fiscal_year' => ['required', 'integer', 'min:2000', 'max:2100'],
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'period_type' => ['required', 'in:annual,quarterly,monthly'],
|
|
'period_start' => ['required', 'date'],
|
|
'period_end' => ['required', 'date', 'after:period_start'],
|
|
'notes' => ['nullable', 'string'],
|
|
]);
|
|
|
|
$budget = Budget::create([
|
|
...$validated,
|
|
'status' => Budget::STATUS_DRAFT,
|
|
'created_by_user_id' => $request->user()->id,
|
|
]);
|
|
|
|
AuditLogger::log('budget.created', $budget, $validated);
|
|
|
|
return redirect()
|
|
->route('admin.budgets.edit', $budget)
|
|
->with('status', __('Budget created successfully. Add budget items below.'));
|
|
}
|
|
|
|
public function show(Budget $budget)
|
|
{
|
|
$budget->load([
|
|
'createdBy',
|
|
'approvedBy',
|
|
'budgetItems.chartOfAccount',
|
|
'budgetItems' => fn($q) => $q->orderBy('chart_of_account_id'),
|
|
]);
|
|
|
|
// Group budget items by account type
|
|
$incomeItems = $budget->budgetItems->filter(fn($item) => $item->chartOfAccount->isIncome());
|
|
$expenseItems = $budget->budgetItems->filter(fn($item) => $item->chartOfAccount->isExpense());
|
|
|
|
return view('admin.budgets.show', [
|
|
'budget' => $budget,
|
|
'incomeItems' => $incomeItems,
|
|
'expenseItems' => $expenseItems,
|
|
]);
|
|
}
|
|
|
|
public function edit(Budget $budget)
|
|
{
|
|
if (!$budget->canBeEdited()) {
|
|
return redirect()
|
|
->route('admin.budgets.show', $budget)
|
|
->with('error', __('This budget cannot be edited.'));
|
|
}
|
|
|
|
$budget->load(['budgetItems.chartOfAccount']);
|
|
|
|
// Get all active 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();
|
|
|
|
return view('admin.budgets.edit', [
|
|
'budget' => $budget,
|
|
'incomeAccounts' => $incomeAccounts,
|
|
'expenseAccounts' => $expenseAccounts,
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, Budget $budget)
|
|
{
|
|
if (!$budget->canBeEdited()) {
|
|
abort(403, 'This budget cannot be edited.');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'period_start' => ['required', 'date'],
|
|
'period_end' => ['required', 'date', 'after:period_start'],
|
|
'notes' => ['nullable', 'string'],
|
|
'budget_items' => ['nullable', 'array'],
|
|
'budget_items.*.chart_of_account_id' => ['required', 'exists:chart_of_accounts,id'],
|
|
'budget_items.*.budgeted_amount' => ['required', 'numeric', 'min:0'],
|
|
'budget_items.*.notes' => ['nullable', 'string'],
|
|
]);
|
|
|
|
DB::transaction(function () use ($budget, $validated, $request) {
|
|
// Update budget
|
|
$budget->update([
|
|
'name' => $validated['name'],
|
|
'period_start' => $validated['period_start'],
|
|
'period_end' => $validated['period_end'],
|
|
'notes' => $validated['notes'] ?? null,
|
|
]);
|
|
|
|
// Delete existing budget items and recreate
|
|
$budget->budgetItems()->delete();
|
|
|
|
// Create new budget items
|
|
if (!empty($validated['budget_items'])) {
|
|
foreach ($validated['budget_items'] as $itemData) {
|
|
if ($itemData['budgeted_amount'] > 0) {
|
|
BudgetItem::create([
|
|
'budget_id' => $budget->id,
|
|
'chart_of_account_id' => $itemData['chart_of_account_id'],
|
|
'budgeted_amount' => $itemData['budgeted_amount'],
|
|
'notes' => $itemData['notes'] ?? null,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
AuditLogger::log('budget.updated', $budget, [
|
|
'user' => $request->user()->name,
|
|
'items_count' => count($validated['budget_items'] ?? []),
|
|
]);
|
|
});
|
|
|
|
return redirect()
|
|
->route('admin.budgets.show', $budget)
|
|
->with('status', __('Budget updated successfully.'));
|
|
}
|
|
|
|
public function submit(Request $request, Budget $budget)
|
|
{
|
|
if (!$budget->isDraft()) {
|
|
abort(403, 'Only draft budgets can be submitted.');
|
|
}
|
|
|
|
if ($budget->budgetItems()->count() === 0) {
|
|
return redirect()
|
|
->route('admin.budgets.edit', $budget)
|
|
->with('error', __('Cannot submit budget without budget items.'));
|
|
}
|
|
|
|
$budget->update(['status' => Budget::STATUS_SUBMITTED]);
|
|
|
|
AuditLogger::log('budget.submitted', $budget, ['submitted_by' => $request->user()->name]);
|
|
|
|
return redirect()
|
|
->route('admin.budgets.show', $budget)
|
|
->with('status', __('Budget submitted for approval.'));
|
|
}
|
|
|
|
public function approve(Request $request, Budget $budget)
|
|
{
|
|
if (!$budget->canBeApproved()) {
|
|
abort(403, 'This budget cannot be approved.');
|
|
}
|
|
|
|
// Check if user has permission (admin or chair)
|
|
$user = $request->user();
|
|
if (!$user->hasRole('chair') && !$user->is_admin && !$user->hasRole('admin')) {
|
|
abort(403, 'Only chair can approve budgets.');
|
|
}
|
|
|
|
$budget->update([
|
|
'status' => Budget::STATUS_APPROVED,
|
|
'approved_by_user_id' => $user->id,
|
|
'approved_at' => now(),
|
|
]);
|
|
|
|
AuditLogger::log('budget.approved', $budget, ['approved_by' => $user->name]);
|
|
|
|
return redirect()
|
|
->route('admin.budgets.show', $budget)
|
|
->with('status', __('Budget approved successfully.'));
|
|
}
|
|
|
|
public function activate(Request $request, Budget $budget)
|
|
{
|
|
if (!$budget->isApproved()) {
|
|
abort(403, 'Only approved budgets can be activated.');
|
|
}
|
|
|
|
$budget->update(['status' => Budget::STATUS_ACTIVE]);
|
|
|
|
AuditLogger::log('budget.activated', $budget, ['activated_by' => $request->user()->name]);
|
|
|
|
return redirect()
|
|
->route('admin.budgets.show', $budget)
|
|
->with('status', __('Budget activated successfully.'));
|
|
}
|
|
|
|
public function close(Request $request, Budget $budget)
|
|
{
|
|
if (!$budget->isActive()) {
|
|
abort(403, 'Only active budgets can be closed.');
|
|
}
|
|
|
|
$budget->update(['status' => Budget::STATUS_CLOSED]);
|
|
|
|
AuditLogger::log('budget.closed', $budget, ['closed_by' => $request->user()->name]);
|
|
|
|
return redirect()
|
|
->route('admin.budgets.show', $budget)
|
|
->with('status', __('Budget closed successfully.'));
|
|
}
|
|
|
|
public function destroy(Request $request, Budget $budget)
|
|
{
|
|
if (!$budget->isDraft()) {
|
|
abort(403, 'Only draft budgets can be deleted.');
|
|
}
|
|
|
|
$fiscalYear = $budget->fiscal_year;
|
|
$budget->delete();
|
|
|
|
AuditLogger::log('budget.deleted', null, [
|
|
'fiscal_year' => $fiscalYear,
|
|
'deleted_by' => $request->user()->name,
|
|
]);
|
|
|
|
return redirect()
|
|
->route('admin.budgets.index')
|
|
->with('status', __('Budget deleted successfully.'));
|
|
}
|
|
}
|