first
This commit is contained in:
131
app/Services/Admin/Management/PennempatanService.php
Normal file
131
app/Services/Admin/Management/PennempatanService.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Admin\Management;
|
||||
|
||||
use App\Models\Position;
|
||||
use App\Models\PositionQuota;
|
||||
use App\Models\PusatMengundi;
|
||||
use App\Models\SaluranMengundi;
|
||||
use App\Models\StaffAssignment;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Collection as BaseCollection;
|
||||
|
||||
class PennempatanService
|
||||
{
|
||||
/**
|
||||
* Summary per pusat for the dashboard vacancy table.
|
||||
*
|
||||
* @return Collection<int, PusatMengundi>
|
||||
*/
|
||||
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<int, array{saluran: SaluranMengundi|null, label: string, entries: 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<int, PositionQuota> $quotas
|
||||
* @param BaseCollection<string, Collection> $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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user