*/ public function pusatsWithVacancySummary(): Collection { return PusatMengundi::query() ->with([ 'dun', 'election', 'saluranMengundis', 'positionQuotas', ]) ->withCount(['saluranMengundis']) ->get() ->each(function (PusatMengundi $pusat): void { $totalQuota = $pusat->positionQuotas->sum('quota'); $filled = StaffAssignment::query() ->where('pusat_mengundi_id', $pusat->id) ->where('status', 'active') ->count(); $pusat->setAttribute('total_quota', $totalQuota); $pusat->setAttribute('filled', $filled); $pusat->setAttribute('vacant', max(0, $totalQuota - $filled)); // PPM name $ppmPosition = Position::query()->where('code', 'PPM')->first(); $ppmAssignment = $ppmPosition ? StaffAssignment::query() ->with('application') ->where('pusat_mengundi_id', $pusat->id) ->where('position_id', $ppmPosition->id) ->where('status', 'active') ->whereNull('saluran_mengundi_id') ->first() : null; $pusat->setAttribute('ppm_name', $ppmAssignment?->application?->name ?? '-'); }) ->filter(fn (PusatMengundi $p): bool => $p->vacant > 0) ->sortByDesc('vacant') ->values(); } /** * Structured breakdown of quotas + assignments for a specific pusat. * * @return BaseCollection */ public function detailForPusat(PusatMengundi $pusat): BaseCollection { $quotas = PositionQuota::query() ->with('position') ->where('pusat_mengundi_id', $pusat->id) ->get() ->groupBy(fn (PositionQuota $q): string => $q->saluran_mengundi_id ?? 'pusat'); $assignments = StaffAssignment::query() ->with(['application', 'position']) ->where('pusat_mengundi_id', $pusat->id) ->where('status', 'active') ->get() ->groupBy(fn (StaffAssignment $a): string => ($a->saluran_mengundi_id ?? 'pusat').'_'.$a->position_id); $salurans = SaluranMengundi::query() ->where('pusat_mengundi_id', $pusat->id) ->orderBy('number') ->get() ->keyBy('id'); $sections = collect(); // Pusat-level section first if ($quotas->has('pusat')) { $sections->push($this->buildSection(null, 'Peringkat Pusat (Saluran 0)', $quotas->get('pusat'), $assignments)); } // Per-saluran sections foreach ($salurans as $saluran) { $saluranQuotas = $quotas->get((string) $saluran->id, collect()); $sections->push($this->buildSection($saluran, 'Saluran '.$saluran->number, $saluranQuotas, $assignments)); } return $sections; } /** * @param Collection $quotas * @param BaseCollection $assignments * @return array{saluran: SaluranMengundi|null, label: string, entries: BaseCollection} */ private function buildSection(?SaluranMengundi $saluran, string $label, $quotas, BaseCollection $assignments): array { $scopeKey = $saluran ? (string) $saluran->id : 'pusat'; $entries = $quotas->map(function (PositionQuota $quota) use ($scopeKey, $assignments) { $key = $scopeKey.'_'.$quota->position_id; $filled = $assignments->get($key, collect()); $vacant = max(0, $quota->quota - $filled->count()); return [ 'quota' => $quota, 'position' => $quota->position, 'assignments' => $filled, 'vacant' => $vacant, ]; })->values(); return [ 'saluran' => $saluran, 'label' => $label, 'entries' => $entries, ]; } }