validate([ 'from' => ['nullable', 'date'], 'to' => ['nullable', 'date'], 'barangay_id' => ['nullable', 'integer'], 'tenant_id' => ['nullable', 'integer'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:500'], ]); $from = isset($data['from']) ? Carbon::parse($data['from'])->toDateString() : Carbon::now()->subDays(30)->toDateString(); $to = isset($data['to']) ? Carbon::parse($data['to'])->toDateString() : Carbon::today()->toDateString(); $perPage = (int) ($data['per_page'] ?? 50); $tenant = $this->resolveTargetTenant($request); return Tenancy::withTenant($tenant, function () use ($from, $to, $data, $perPage) { $query = DailyCollectionStat::query() ->whereBetween('date', [$from, $to]) ->when($data['barangay_id'] ?? null, fn ($q, $id) => $q->where('barangay_id', $id)); $totals = [ 'total_scans' => (int) $query->sum('total_scans'), 'total_weight_kg' => (int) $query->sum('total_weight_kg'), 'unique_households' => (int) $query->sum('unique_households'), ]; $rows = $query->orderBy('date', 'desc')->paginate($perPage); return $this->ok( $rows->map(fn ($r) => [ 'date' => $r->date->toDateString(), 'barangay_id' => $r->barangay_id, 'total_scans' => $r->total_scans, 'total_weight_kg' => $r->total_weight_kg, 'unique_households' => $r->unique_households, 'missed_pickups' => $r->missed_pickups, ]), null, array_merge($totals, [ 'page' => $rows->currentPage(), 'per_page' => $rows->perPage(), 'total' => $rows->total(), 'last_page' => $rows->lastPage(), 'from' => $from, 'to' => $to, ]) ); }); } public function tripPerformance(Request $request): JsonResponse { $data = $request->validate([ 'from' => ['nullable', 'date'], 'to' => ['nullable', 'date'], 'route_id' => ['nullable', 'integer'], 'tenant_id' => ['nullable', 'integer'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:500'], ]); $from = isset($data['from']) ? Carbon::parse($data['from'])->toDateString() : Carbon::now()->subWeeks(8)->toDateString(); $to = isset($data['to']) ? Carbon::parse($data['to'])->toDateString() : Carbon::today()->toDateString(); $perPage = (int) ($data['per_page'] ?? 50); $tenant = $this->resolveTargetTenant($request); return Tenancy::withTenant($tenant, function () use ($from, $to, $data, $perPage) { $rows = WeeklyRoutePerformance::with('route') ->whereBetween('week_start_date', [$from, $to]) ->when($data['route_id'] ?? null, fn ($q, $id) => $q->where('route_id', $id)) ->orderBy('week_start_date', 'desc') ->paginate($perPage); return $this->ok( $rows->map(fn ($r) => [ 'week_start' => $r->week_start_date->toDateString(), 'route_code' => $r->route?->code, 'on_time_rate_percent' => $r->on_time_rate_percent, 'avg_trip_duration_minutes' => $r->avg_trip_duration_minutes, 'completion_rate_percent' => $r->completion_rate_percent, 'trips_count' => $r->trips_count, ]), null, [ 'page' => $rows->currentPage(), 'per_page' => $rows->perPage(), 'total' => $rows->total(), 'last_page' => $rows->lastPage(), 'from' => $from, 'to' => $to, ] ); }); } public function storeSales(Request $request): JsonResponse { $data = $request->validate([ 'from' => ['nullable', 'date'], 'to' => ['nullable', 'date'], 'store_id' => ['nullable', 'integer'], 'tenant_id' => ['nullable', 'integer'], 'per_page' => ['nullable', 'integer', 'min:1', 'max:500'], ]); $from = isset($data['from']) ? Carbon::parse($data['from'])->startOfMonth()->toDateString() : Carbon::now()->subMonths(6)->startOfMonth()->toDateString(); $to = isset($data['to']) ? Carbon::parse($data['to'])->endOfMonth()->toDateString() : Carbon::today()->endOfMonth()->toDateString(); $perPage = (int) ($data['per_page'] ?? 50); $tenant = $this->resolveTargetTenant($request); return Tenancy::withTenant($tenant, function () use ($from, $to, $data, $perPage) { $query = MonthlyStoreSale::with('store') ->whereBetween('month_start_date', [$from, $to]) ->when($data['store_id'] ?? null, fn ($q, $id) => $q->where('store_id', $id)); $totals = [ 'total_quantity' => (int) $query->sum('total_quantity_sold'), 'total_retail_centavos' => (int) $query->sum('total_retail_centavos'), 'total_commission_centavos' => (int) $query->sum('total_commission_centavos'), ]; $rows = $query->orderBy('month_start_date', 'desc')->paginate($perPage); return $this->ok( $rows->map(fn ($r) => [ 'month' => $r->month_start_date->toDateString(), 'store_name' => $r->store?->business_name, 'quantity' => $r->total_quantity_sold, 'retail_centavos' => $r->total_retail_centavos, 'commission_centavos' => $r->total_commission_centavos, ]), null, array_merge($totals, [ 'page' => $rows->currentPage(), 'per_page' => $rows->perPage(), 'total' => $rows->total(), 'last_page' => $rows->lastPage(), 'from' => $from, 'to' => $to, ]) ); }); } public function complianceCsv(Request $request): StreamedResponse { $data = $request->validate([ 'from' => ['required', 'date'], 'to' => ['required', 'date'], 'tenant_id' => ['nullable', 'integer'], ]); $from = Carbon::parse($data['from'])->startOfDay(); $to = Carbon::parse($data['to'])->endOfDay(); $tenant = $this->resolveTargetTenant($request); $headers = [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="verde-compliance-'.$from->toDateString().'-'.$to->toDateString().'.csv"', ]; return response()->streamDownload(function () use ($from, $to, $tenant) { $out = fopen('php://output', 'w'); fputcsv($out, ['released_at', 'trip_number', 'dumpsite', 'permit_number', 'weight_kg', 'gate_pass', 'attendant']); Tenancy::withTenant($tenant, function () use ($from, $to, $out) { DumpsiteRelease::with(['dumpsite', 'trip']) ->whereBetween('released_at', [$from, $to]) ->orderBy('released_at') ->chunk(500, function ($rows) use ($out) { foreach ($rows as $r) { fputcsv($out, [ $r->released_at?->toIso8601String(), $r->trip?->trip_number, $r->dumpsite?->name, $r->dumpsite?->permit_number, $r->weight_kg, $r->gate_pass_number, $r->dumpsite_attendant_name, ]); } }); }); fclose($out); }, 'verde-compliance.csv', $headers); } public function rebuild(Request $request): JsonResponse { $date = $request->input('date') ? Carbon::parse($request->input('date')) : Carbon::yesterday(); $daily = $this->aggregator->rebuildDailyCollectionStats($date); $weekly = $this->aggregator->rebuildWeeklyRoutePerformance($date); $monthly = $this->aggregator->rebuildMonthlyStoreSales($date); return $this->ok([ 'reference_date' => $date->toDateString(), 'daily_buckets' => $daily, 'weekly_buckets' => $weekly, 'monthly_buckets' => $monthly, ], 'Aggregations rebuilt'); } private function resolveTargetTenant(Request $request): ?Tenant { if (optional($request->user())->isSuperAdmin() && $request->filled('tenant_id')) { return Tenant::find($request->input('tenant_id')); } return Tenancy::current(); } }