feat: complete all plan phases — reports, cleanup market fragments
Some checks failed
tests / PHP 8.2 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.3 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.4 (swoole-6.0) (push) Has been cancelled

- Add Reports page with population/household/document/blotter/budget/project views
- Add ReportsController with year-filtered queries for all report types
- Add /reports module to config/modules.php
- Register /barangay/reports in VueRouteMap and web.php
- Remove unused market Home fragments (HomeCoopMember, HomeStoreOwner, etc.)
- Remove leftover market Components/Market/ directory
- Add Reports card to Home.vue admin quick access
This commit is contained in:
Jonathan Sykes
2026-06-07 03:15:04 +08:00
parent fbb7e3ff37
commit bee4a1f5ab
25 changed files with 584 additions and 4065 deletions

View File

@@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Barangay;
use App\Enums\UserActions;
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
use App\Http\Controllers\Helpers\ResponseHelper;
use App\Models\Barangay\Blotter;
use App\Models\Barangay\BarangayBudget;
use App\Models\Barangay\BarangayProject;
use App\Models\Barangay\DocumentRequest;
use App\Models\Barangay\Household;
use App\Models\Barangay\Resident;
use Carbon\Carbon;
use Hypervel\Http\Request;
use Hypervel\Support\Facades\Auth;
use Hypervel\Support\Facades\DB;
class ReportsController
{
public function generate(Request $request)
{
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewGlobalReports)) {
return ResponseHelper::returnUnauthorized();
}
$type = $request->input('type', 'population');
$year = (int) $request->input('year', date('Y'));
$data = match ($type) {
'population' => $this->populationReport(),
'households' => $this->householdsReport(),
'documents' => $this->documentsReport($year),
'blotters' => $this->blottersReport($year),
'budget' => $this->budgetReport($year),
'projects' => $this->projectsReport($year),
default => [],
};
return response()->json(['success' => true, 'data' => $data]);
}
private function populationReport(): array
{
$total = Resident::count();
$active = Resident::where('is_active', true)->count();
$voters = Resident::where('voter_status', true)->count();
$genderBreakdown = Resident::select('gender', DB::raw('count(*) as count'))
->whereNotNull('gender')
->groupBy('gender')
->pluck('count', 'gender')
->toArray();
$civilBreakdown = Resident::select('civil_status', DB::raw('count(*) as count'))
->whereNotNull('civil_status')
->groupBy('civil_status')
->pluck('count', 'civil_status')
->toArray();
$purokBreakdown = Resident::select('purok', DB::raw('count(*) as count'))
->whereNotNull('purok')
->groupBy('purok')
->orderBy('purok')
->pluck('count', 'purok')
->toArray();
// Age groups
$now = Carbon::now();
$ageGroups = [
'012 (Children)' => [0, 12],
'1317 (Youth)' => [13, 17],
'1830 (Young Adult)' => [18, 30],
'3159 (Adult)' => [31, 59],
'60+ (Senior)' => [60, 150],
];
$ageBreakdown = [];
foreach ($ageGroups as $label => [$min, $max]) {
$from = $now->copy()->subYears($max)->format('Y-m-d');
$to = $now->copy()->subYears($min)->format('Y-m-d');
$ageBreakdown[$label] = Resident::whereBetween('date_of_birth', [$from, $to])->count();
}
return compact('total', 'active', 'voters', 'genderBreakdown', 'civilBreakdown', 'purokBreakdown', 'ageBreakdown') + [
'total_residents' => $total,
'active_residents' => $active,
'registered_voters' => $voters,
'gender_breakdown' => $genderBreakdown,
'civil_status_breakdown'=> $civilBreakdown,
'purok_breakdown' => $purokBreakdown,
'age_breakdown' => $ageBreakdown,
];
}
private function householdsReport(): array
{
$total = Household::count();
$withElectricity = Household::where('has_electricity', true)->count();
$withWater = Household::where('has_water', true)->count();
$avgMembers = round(Household::withCount('chapterMembers')->avg('chapter_members_count') ?? 0, 1);
$byOwnership = Household::select('ownership_type', DB::raw('count(*) as count'))
->groupBy('ownership_type')
->pluck('count', 'ownership_type')
->toArray();
return [
'total_households' => $total,
'with_electricity' => $withElectricity,
'with_water' => $withWater,
'avg_members' => $avgMembers,
'by_ownership' => $byOwnership,
];
}
private function documentsReport(int $year): array
{
$base = DocumentRequest::whereYear('created_at', $year);
$total = (clone $base)->count();
$claimed = (clone $base)->where('status', 'CLAIMED')->count();
$revenue = (clone $base)->where('payment_status', 'PAID')->sum('base_fee');
$byStatus = (clone $base)->select('status', DB::raw('count(*) as count'))
->groupBy('status')
->pluck('count', 'status')
->toArray();
$byType = (clone $base)->select(
'barangay_request_types.name',
DB::raw('count(*) as count'),
DB::raw('sum(case when barangay_document_requests.payment_status = \'PAID\' then barangay_document_requests.base_fee else 0 end) as revenue')
)
->leftJoin('barangay_request_types', 'barangay_request_types.id', '=', 'barangay_document_requests.request_type_id')
->groupBy('barangay_request_types.name')
->get()
->toArray();
return compact('total', 'claimed', 'revenue', 'byStatus', 'byType') + [
'total_revenue' => $revenue,
'by_status' => $byStatus,
'by_type' => $byType,
];
}
private function blottersReport(int $year): array
{
$base = Blotter::whereYear('created_at', $year);
$total = (clone $base)->count();
$resolved = (clone $base)->whereIn('status', ['RESOLVED', 'SETTLED'])->count();
$pending = (clone $base)->whereIn('status', ['FILED', 'FOR_HEARING'])->count();
$byStatus = (clone $base)->select('status', DB::raw('count(*) as count'))
->groupBy('status')
->pluck('count', 'status')
->toArray();
$byType = (clone $base)->select('incident_type', DB::raw('count(*) as count'))
->groupBy('incident_type')
->pluck('count', 'incident_type')
->toArray();
return [
'total' => $total,
'resolved' => $resolved,
'pending' => $pending,
'by_status' => $byStatus,
'by_incident_type'=> $byType,
];
}
private function budgetReport(int $year): array
{
$income = BarangayBudget::income()->byYear($year)->sum('amount');
$expense = BarangayBudget::expense()->byYear($year)->sum('amount');
$balance = $income - $expense;
$bySource = BarangayBudget::select('source', 'category', DB::raw('sum(amount) as amount'))
->byYear($year)
->groupBy('source', 'category')
->orderBy('category')
->get()
->toArray();
return [
'summary' => compact('income', 'expense', 'balance') + [
'total_income' => $income,
'total_expense' => $expense,
],
'by_source' => $bySource,
];
}
private function projectsReport(int $year): array
{
$base = BarangayProject::whereYear('created_at', $year);
$total = (clone $base)->count();
$ongoing = (clone $base)->where('status', 'ONGOING')->count();
$completed = (clone $base)->where('status', 'COMPLETED')->count();
$budget = (clone $base)->sum('budget');
$byStatus = (clone $base)->select('status', DB::raw('count(*) as count'))
->groupBy('status')
->pluck('count', 'status')
->toArray();
$byType = (clone $base)->select('type', DB::raw('count(*) as count'))
->groupBy('type')
->get()
->toArray();
return [
'total' => $total,
'ongoing' => $ongoing,
'completed' => $completed,
'total_budget' => $budget,
'by_status' => $byStatus,
'by_type' => $byType,
];
}
}