This commit is contained in:
Saufi
2026-06-03 08:51:22 +08:00
commit a14d43fe34
347 changed files with 38197 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
<?php
namespace App\Services\Public;
use App\Models\Application;
use App\Models\Position;
use App\Models\PositionQuota;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use App\Models\StaffAssignment;
use App\Models\User;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Collection;
class KtmVacancyService
{
private const RESERVED_APPLICATION_STATUSES = [
'submitted',
'under_ppm_review',
'approved',
];
/**
* @param array<int, string> $roleCodes
* @return array<string, array<int, array{id: int, label: string, remaining: int}>>
*/
public function optionsForRoles(PusatMengundi $pusatMengundi, array $roleCodes = ['KP', 'KPDP'], ?int $ignoreApplicationId = null): array
{
$options = [];
foreach ($roleCodes as $roleCode) {
$options[$roleCode] = $this->availableKtmAssignments($pusatMengundi, $roleCode, $ignoreApplicationId)
->map(function (StaffAssignment $assignment) use ($roleCode, $ignoreApplicationId): array {
$user = $assignment->user;
$saluranMengundi = $assignment->saluranMengundi;
return [
'id' => $assignment->id,
'label' => trim(
($user instanceof User ? $user->name : 'KTM tanpa nama')
.' - Saluran '
.($saluranMengundi instanceof SaluranMengundi ? $saluranMengundi->number : '-'),
),
'remaining' => $this->remainingVacancy($assignment, $roleCode, $ignoreApplicationId),
];
})
->values()
->all();
}
return $options;
}
public function hasVacancyForAssignment(int $assignmentId, PusatMengundi $pusatMengundi, string $roleCode, ?int $ignoreApplicationId = null): bool
{
$ktmPosition = Position::query()->where('code', 'KTM')->first();
if ($ktmPosition === null) {
return false;
}
$assignment = StaffAssignment::query()
->with(['saluranMengundi', 'user'])
->where('id', $assignmentId)
->where('election_id', $pusatMengundi->election_id)
->where('pusat_mengundi_id', $pusatMengundi->id)
->where('position_id', $ktmPosition->id)
->where('status', 'active')
->whereNotNull('saluran_mengundi_id')
->first();
return $assignment instanceof StaffAssignment
&& $this->remainingVacancy($assignment, $roleCode, $ignoreApplicationId) > 0;
}
public function remainingForAssignment(StaffAssignment $assignment, string $roleCode, ?int $ignoreApplicationId = null): int
{
return $this->remainingVacancy($assignment, $roleCode, $ignoreApplicationId);
}
/**
* @return Collection<int, StaffAssignment>
*/
private function availableKtmAssignments(PusatMengundi $pusatMengundi, string $roleCode, ?int $ignoreApplicationId = null): Collection
{
return $this->ktmAssignments($pusatMengundi)
->filter(fn (StaffAssignment $assignment): bool => $this->remainingVacancy($assignment, $roleCode, $ignoreApplicationId) > 0)
->values();
}
/**
* @return EloquentCollection<int, StaffAssignment>
*/
private function ktmAssignments(PusatMengundi $pusatMengundi): EloquentCollection
{
$ktmPosition = Position::query()->where('code', 'KTM')->first();
if ($ktmPosition === null) {
return new EloquentCollection;
}
return StaffAssignment::query()
->with(['saluranMengundi', 'user'])
->where('election_id', $pusatMengundi->election_id)
->where('pusat_mengundi_id', $pusatMengundi->id)
->where('position_id', $ktmPosition->id)
->where('status', 'active')
->whereNotNull('saluran_mengundi_id')
->whereNotNull('user_id')
->orderBy('saluran_mengundi_id')
->get();
}
private function remainingVacancy(StaffAssignment $ktmAssignment, string $roleCode, ?int $ignoreApplicationId = null): int
{
$position = Position::query()->where('code', $roleCode)->first();
if ($position === null || $ktmAssignment->saluran_mengundi_id === null) {
return 0;
}
$quota = PositionQuota::query()
->where('election_id', $ktmAssignment->election_id)
->where('pusat_mengundi_id', $ktmAssignment->pusat_mengundi_id)
->where('saluran_mengundi_id', $ktmAssignment->saluran_mengundi_id)
->where('position_id', $position->id)
->value('quota');
if ($quota === null) {
return 0;
}
$assignedCount = StaffAssignment::query()
->where('reports_to_assignment_id', $ktmAssignment->id)
->where('position_id', $position->id)
->where('status', 'active')
->count();
$reservedQuery = Application::query()
->where('selected_ktm_assignment_id', $ktmAssignment->id)
->where('requested_position_id', $position->id)
->whereIn('status', self::RESERVED_APPLICATION_STATUSES);
if ($ignoreApplicationId !== null) {
$reservedQuery->where('id', '!=', $ignoreApplicationId);
}
$reservedCount = $reservedQuery->count();
return max(0, (int) $quota - $assignedCount - $reservedCount);
}
}