first
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Admin\Management;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\BankVerification;
|
||||
use App\Models\Election;
|
||||
use App\Models\Position;
|
||||
use App\Models\PusatMengundi;
|
||||
use App\Models\SaluranMengundi;
|
||||
use App\Models\StaffAssignment;
|
||||
use App\Services\Admin\OperationalAccountService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AdminApplicationManagementService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AdminPostCloseNoteService $postCloseNoteService,
|
||||
private readonly OperationalAccountService $operationalAccountService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function create(User $actor, array $data): Application
|
||||
{
|
||||
return DB::transaction(function () use ($actor, $data): Application {
|
||||
$election = Election::query()->findOrFail($data['election_id']);
|
||||
$this->postCloseNoteService->requireIfClosed($election, $data['note'] ?? null);
|
||||
/** @var PusatMengundi $pusat */
|
||||
$pusat = PusatMengundi::query()->findOrFail($data['pusat_mengundi_id']);
|
||||
|
||||
if ((int) $pusat->election_id !== (int) $election->id) {
|
||||
throw ValidationException::withMessages([
|
||||
'pusat_mengundi_id' => 'Pusat Mengundi tidak berada dalam pilihanraya yang sama.',
|
||||
]);
|
||||
}
|
||||
|
||||
$application = Application::query()->create([
|
||||
'election_id' => $election->id,
|
||||
'pusat_mengundi_id' => $pusat->id,
|
||||
'selected_ktm_assignment_id' => $data['selected_ktm_assignment_id'] ?? null,
|
||||
'public_uuid' => (string) Str::uuid(),
|
||||
'source' => 'admin_manual',
|
||||
'status' => 'submitted',
|
||||
'name' => $data['name'],
|
||||
'ic_number' => $this->normalizeIc($data['ic_number']),
|
||||
'phone_number' => $data['phone_number'],
|
||||
'email' => $data['email'] ?? null,
|
||||
'address' => $data['address'] ?? null,
|
||||
'requested_position_id' => $data['requested_position_id'],
|
||||
'bank_name' => $data['bank_name'] ?? null,
|
||||
'bank_account_number' => $data['bank_account_number'] ?? null,
|
||||
'created_by_user_id' => $actor->id,
|
||||
]);
|
||||
|
||||
$application->statusHistories()->create([
|
||||
'from_status' => null,
|
||||
'to_status' => 'submitted',
|
||||
'changed_by_user_id' => $actor->id,
|
||||
'note' => $data['note'] ?? 'Manual entry by Admin.',
|
||||
]);
|
||||
|
||||
BankVerification::query()->firstOrCreate(
|
||||
['application_id' => $application->id],
|
||||
['status' => 'pending'],
|
||||
);
|
||||
|
||||
$this->postCloseNoteService->recordIfClosed($election, $application, $actor, $data['note'] ?? null, 'admin_manual_entry_after_close');
|
||||
|
||||
activity('admin_management')
|
||||
->performedOn($application)
|
||||
->causedBy($actor)
|
||||
->withProperties(['source' => 'admin_manual'])
|
||||
->log('application_created_by_admin');
|
||||
|
||||
return $application;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function update(Application $application, User $actor, array $data): Application
|
||||
{
|
||||
return DB::transaction(function () use ($application, $actor, $data): Application {
|
||||
$application->loadMissing('election');
|
||||
$election = $application->election;
|
||||
if (! $election instanceof Election) {
|
||||
throw ValidationException::withMessages([
|
||||
'election_id' => 'Pilihanraya permohonan tidak sah.',
|
||||
]);
|
||||
}
|
||||
$this->postCloseNoteService->requireIfClosed($election, $data['note'] ?? null);
|
||||
|
||||
$before = $application->only([
|
||||
'pusat_mengundi_id',
|
||||
'selected_ktm_assignment_id',
|
||||
'name',
|
||||
'ic_number',
|
||||
'phone_number',
|
||||
'email',
|
||||
'address',
|
||||
'requested_position_id',
|
||||
'approved_position_id',
|
||||
'bank_name',
|
||||
'bank_account_number',
|
||||
'status',
|
||||
]);
|
||||
|
||||
$application->forceFill([
|
||||
'pusat_mengundi_id' => $data['pusat_mengundi_id'],
|
||||
'selected_ktm_assignment_id' => $data['selected_ktm_assignment_id'] ?? null,
|
||||
'name' => $data['name'],
|
||||
'ic_number' => $this->normalizeIc($data['ic_number']),
|
||||
'phone_number' => $data['phone_number'],
|
||||
'email' => $data['email'] ?? null,
|
||||
'address' => $data['address'] ?? null,
|
||||
'requested_position_id' => $data['requested_position_id'],
|
||||
'approved_position_id' => $data['approved_position_id'] ?? null,
|
||||
'bank_name' => $data['bank_name'] ?? null,
|
||||
'bank_account_number' => $data['bank_account_number'] ?? null,
|
||||
'status' => $data['status'],
|
||||
])->save();
|
||||
|
||||
$after = $application->only(array_keys($before));
|
||||
|
||||
$application->statusHistories()->create([
|
||||
'from_status' => $before['status'],
|
||||
'to_status' => $application->status,
|
||||
'changed_by_user_id' => $actor->id,
|
||||
'note' => $data['note'] ?? 'Application updated by Admin.',
|
||||
'metadata' => [
|
||||
'before' => $before,
|
||||
'after' => $after,
|
||||
],
|
||||
]);
|
||||
|
||||
$this->postCloseNoteService->recordIfClosed($election, $application, $actor, $data['note'] ?? null, 'admin_application_edit_after_close');
|
||||
|
||||
activity('admin_management')
|
||||
->performedOn($application)
|
||||
->causedBy($actor)
|
||||
->withProperties(['before' => $before, 'after' => $after])
|
||||
->log('application_updated_by_admin');
|
||||
|
||||
return $application;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function assign(Application $application, User $actor, array $data): StaffAssignment
|
||||
{
|
||||
return DB::transaction(function () use ($application, $actor, $data): StaffAssignment {
|
||||
$application->loadMissing('election');
|
||||
$election = $application->election;
|
||||
if (! $election instanceof Election) {
|
||||
throw ValidationException::withMessages([
|
||||
'election_id' => 'Pilihanraya permohonan tidak sah.',
|
||||
]);
|
||||
}
|
||||
$this->postCloseNoteService->requireIfClosed($election, $data['note'] ?? null);
|
||||
|
||||
/** @var Position $position */
|
||||
$position = Position::query()->findOrFail($data['position_id']);
|
||||
/** @var PusatMengundi $pusat */
|
||||
$pusat = PusatMengundi::query()->findOrFail($data['pusat_mengundi_id']);
|
||||
|
||||
if ((int) $pusat->election_id !== (int) $election->id) {
|
||||
throw ValidationException::withMessages([
|
||||
'pusat_mengundi_id' => 'Pusat Mengundi tidak berada dalam pilihanraya yang sama.',
|
||||
]);
|
||||
}
|
||||
|
||||
$reportsToAssignmentId = $data['reports_to_assignment_id'] ?? null;
|
||||
$saluranMengundiId = $data['saluran_mengundi_id'] ?? null;
|
||||
|
||||
if ($saluranMengundiId) {
|
||||
/** @var SaluranMengundi $saluran */
|
||||
$saluran = SaluranMengundi::query()->findOrFail($saluranMengundiId);
|
||||
if ((int) $saluran->pusat_mengundi_id !== (int) $pusat->id) {
|
||||
throw ValidationException::withMessages([
|
||||
'saluran_mengundi_id' => 'Saluran Mengundi tidak berada di bawah Pusat Mengundi yang dipilih.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($reportsToAssignmentId) {
|
||||
/** @var StaffAssignment $ktmAssignment */
|
||||
$ktmAssignment = StaffAssignment::query()
|
||||
->with('position')
|
||||
->whereKey($reportsToAssignmentId)
|
||||
->where('election_id', $election->id)
|
||||
->where('pusat_mengundi_id', $pusat->id)
|
||||
->where('status', 'active')
|
||||
->firstOrFail();
|
||||
|
||||
$ktmPosition = $ktmAssignment->position;
|
||||
if (! $ktmPosition instanceof Position || $ktmPosition->code !== 'KTM') {
|
||||
throw ValidationException::withMessages([
|
||||
'reports_to_assignment_id' => 'Ketua rujukan mestilah tugasan KTM aktif.',
|
||||
]);
|
||||
}
|
||||
|
||||
$saluranMengundiId = $ktmAssignment->saluran_mengundi_id;
|
||||
}
|
||||
|
||||
$previousStatus = $application->status;
|
||||
$previousPositionId = $application->approved_position_id ?: $application->requested_position_id;
|
||||
|
||||
$application->forceFill([
|
||||
'pusat_mengundi_id' => $pusat->id,
|
||||
'selected_ktm_assignment_id' => $reportsToAssignmentId,
|
||||
'approved_position_id' => $position->id,
|
||||
'approved_by_user_id' => $actor->id,
|
||||
'approved_at' => now(),
|
||||
'status' => 'assigned',
|
||||
])->save();
|
||||
|
||||
$assignment = StaffAssignment::query()->updateOrCreate(
|
||||
['application_id' => $application->id],
|
||||
[
|
||||
'election_id' => $election->id,
|
||||
'user_id' => $application->user_id,
|
||||
'position_id' => $position->id,
|
||||
'pusat_mengundi_id' => $pusat->id,
|
||||
'saluran_mengundi_id' => $saluranMengundiId,
|
||||
'reports_to_assignment_id' => $reportsToAssignmentId,
|
||||
'status' => $data['status'] ?? 'active',
|
||||
'assigned_by_user_id' => $actor->id,
|
||||
'assigned_at' => now(),
|
||||
'source' => 'admin_direct',
|
||||
],
|
||||
);
|
||||
|
||||
$application->statusHistories()->create([
|
||||
'from_status' => $previousStatus,
|
||||
'to_status' => 'assigned',
|
||||
'changed_by_user_id' => $actor->id,
|
||||
'note' => $data['note'] ?? 'Direct assignment by Admin.',
|
||||
'metadata' => [
|
||||
'approved_position_id' => $position->id,
|
||||
'previous_position_id' => $previousPositionId,
|
||||
'pusat_mengundi_id' => $pusat->id,
|
||||
'saluran_mengundi_id' => $saluranMengundiId,
|
||||
'reports_to_assignment_id' => $reportsToAssignmentId,
|
||||
],
|
||||
]);
|
||||
|
||||
$assignment->histories()->create([
|
||||
'from_position_id' => $previousPositionId,
|
||||
'to_position_id' => $position->id,
|
||||
'to_pusat_mengundi_id' => $pusat->id,
|
||||
'to_saluran_mengundi_id' => $saluranMengundiId,
|
||||
'to_reports_to_assignment_id' => $reportsToAssignmentId,
|
||||
'changed_by_user_id' => $actor->id,
|
||||
'note' => $data['note'] ?? 'Direct assignment by Admin.',
|
||||
]);
|
||||
|
||||
$this->operationalAccountService->provisionForApplication($application, $position);
|
||||
$this->postCloseNoteService->recordIfClosed($election, $assignment, $actor, $data['note'] ?? null, 'admin_assignment_after_close');
|
||||
|
||||
activity('admin_management')
|
||||
->performedOn($assignment)
|
||||
->causedBy($actor)
|
||||
->withProperties([
|
||||
'application_id' => $application->id,
|
||||
'position_code' => $position->code,
|
||||
'pusat_mengundi_id' => $pusat->id,
|
||||
'saluran_mengundi_id' => $saluranMengundiId,
|
||||
'reports_to_assignment_id' => $reportsToAssignmentId,
|
||||
])
|
||||
->log('staff_assigned_by_admin');
|
||||
|
||||
return $assignment;
|
||||
});
|
||||
}
|
||||
|
||||
private function normalizeIc(mixed $icNumber): string
|
||||
{
|
||||
return preg_replace('/\D+/', '', (string) $icNumber) ?? '';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user