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,39 @@
<?php
namespace App\Http\Controllers\Admin\Management;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\JkmRepresentative;
use App\Models\KkmRepresentative;
use App\Models\PoliceEscort;
use App\Models\StaffAssignment;
use App\Models\WheelchairAllocation;
use App\Services\Admin\Management\WheelchairManagementService;
use Illuminate\View\View;
class AdminManagementController extends Controller
{
public function index(WheelchairManagementService $wheelchairService): View
{
$allocations = WheelchairAllocation::query()->with('transactions')->get();
return view('admin.management.index', [
'counts' => [
'applications' => Application::query()->count(),
'assigned' => Application::query()->where('status', 'assigned')->count(),
'staffAssignments' => StaffAssignment::query()->where('status', 'active')->count(),
'representatives' => PoliceEscort::query()->count() + KkmRepresentative::query()->count() + JkmRepresentative::query()->count(),
'missingDocuments' => Application::query()
->whereDoesntHave('documents', fn ($query) => $query->where('document_type', 'ic_document'))
->orWhereDoesntHave('documents', fn ($query) => $query->where('document_type', 'bank_statement'))
->count(),
'incompleteBank' => Application::query()
->where(fn ($query) => $query->whereNull('bank_name')->orWhereNull('bank_account_number'))
->count(),
'wheelchairAllocated' => $allocations->sum('allocated_quantity'),
'wheelchairOutstanding' => $allocations->sum(fn (WheelchairAllocation $allocation): int => $wheelchairService->outstandingQuantity($allocation)),
],
]);
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Admin\Management;
use App\Http\Concerns\SortsQuery;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Management\AssignApplicationRequest;
use App\Http\Requests\Admin\Management\StoreManualApplicationRequest;
use App\Http\Requests\Admin\Management\UpdateManagedApplicationRequest;
use App\Models\Application;
use App\Models\Election;
use App\Models\Position;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use App\Models\StaffAssignment;
use App\Services\Admin\Management\AdminApplicationManagementService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class ManagedApplicationController extends Controller
{
use SortsQuery;
public function index(Request $request): View
{
$query = Application::query()
->with(['election', 'pusatMengundi', 'requestedPosition', 'approvedPosition', 'selectedKtmAssignment.user'])
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
->when($request->filled('pusat_mengundi_id'), fn ($q) => $q->where('pusat_mengundi_id', $request->integer('pusat_mengundi_id')))
->when($request->filled('position_id'), function ($q) use ($request): void {
$q->where(function ($nested) use ($request): void {
$nested->where('requested_position_id', $request->integer('position_id'))
->orWhere('approved_position_id', $request->integer('position_id'));
});
})
->when($request->filled('q'), function ($q) use ($request): void {
$term = '%'.$request->string('q')->toString().'%';
$q->where(fn ($nested) => $nested
->where('name', 'like', $term)
->orWhere('ic_number', 'like', $term)
->orWhere('email', 'like', $term));
});
$applications = $this->sortedQuery($query, ['name', 'status', 'created_at'], 'created_at', 'desc')
->paginate(15)
->withQueryString();
return view('admin.management.applications.index', [
'applications' => $applications,
'positions' => Position::query()->orderBy('sort_order')->orderBy('name')->get(),
'pusats' => PusatMengundi::query()->orderBy('name')->get(),
'filters' => $request->only(['status', 'pusat_mengundi_id', 'position_id', 'q']),
]);
}
public function create(): View
{
return view('admin.management.applications.create', $this->formData([
'application' => new Application,
]));
}
public function store(StoreManualApplicationRequest $request, AdminApplicationManagementService $service): RedirectResponse
{
$application = $service->create($request->user(), $request->validated());
return redirect()->route('admin.management.applications.edit', $application->public_uuid)
->with('status', 'Permohonan manual telah disimpan.');
}
public function edit(Application $application): View
{
return view('admin.management.applications.edit', $this->formData([
'application' => $application->load(['election.settings', 'pusatMengundi', 'requestedPosition', 'approvedPosition', 'documents', 'staffAssignments.position', 'staffAssignments.saluranMengundi', 'statusHistories']),
]));
}
public function update(UpdateManagedApplicationRequest $request, Application $application, AdminApplicationManagementService $service): RedirectResponse
{
$service->update($application, $request->user(), $request->validated());
return redirect()->route('admin.management.applications.edit', $application->public_uuid)
->with('status', 'Permohonan telah dikemas kini.');
}
public function assign(AssignApplicationRequest $request, Application $application, AdminApplicationManagementService $service): RedirectResponse
{
$service->assign($application, $request->user(), $request->validated());
return redirect()->route('admin.management.applications.edit', $application->public_uuid)
->with('status', 'Tugasan staf telah dikemas kini.');
}
/**
* @param array<string, mixed> $extra
* @return array<string, mixed>
*/
private function formData(array $extra = []): array
{
$ktmPosition = Position::query()->where('code', 'KTM')->first();
return [
...$extra,
'elections' => Election::query()->with('settings')->orderByDesc('id')->get(),
'pusats' => PusatMengundi::query()->with('election')->orderBy('name')->get(),
'salurans' => SaluranMengundi::query()->with('pusatMengundi')->orderBy('number')->get(),
'positions' => Position::query()->where('is_assignable', true)->orderBy('sort_order')->orderBy('name')->get(),
'ktmAssignments' => $ktmPosition
? StaffAssignment::query()
->with(['user', 'pusatMengundi', 'saluranMengundi'])
->where('position_id', $ktmPosition->id)
->where('status', 'active')
->whereNotNull('saluran_mengundi_id')
->orderByDesc('id')
->get()
: collect(),
];
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers\Admin\Management;
use App\Http\Controllers\Controller;
use App\Models\Position;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use App\Models\StaffAssignment;
use App\Services\Admin\Management\PennempatanService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PennempatanController extends Controller
{
public function show(PusatMengundi $pusatMengundi, PennempatanService $service): View
{
$pusatMengundi->load(['dun', 'election', 'saluranMengundis']);
$ppmPosition = Position::query()->where('code', 'PPM')->first();
$ppmAssignment = $ppmPosition
? StaffAssignment::query()
->with('application')
->where('pusat_mengundi_id', $pusatMengundi->id)
->where('position_id', $ppmPosition->id)
->where('status', 'active')
->whereNull('saluran_mengundi_id')
->first()
: null;
return view('admin.management.penempatan.show', [
'pusat' => $pusatMengundi,
'ppm' => $ppmAssignment,
'sections' => $service->detailForPusat($pusatMengundi),
]);
}
public function lepas(Request $request, PusatMengundi $pusatMengundi, StaffAssignment $assignment): RedirectResponse
{
abort_unless($assignment->pusat_mengundi_id === $pusatMengundi->id, 404);
$assignment->update([
'status' => 'inactive',
'assigned_by_user_id' => $request->user()->id,
]);
activity('admin_management')
->performedOn($assignment)
->causedBy($request->user())
->log('assignment_removed_by_admin');
return redirect()->route('admin.management.penempatan.show', $pusatMengundi)
->with('status', 'Petugas telah dilepaskan dari tugasan.');
}
public function pindahForm(PusatMengundi $pusatMengundi, StaffAssignment $assignment): View
{
abort_unless($assignment->pusat_mengundi_id === $pusatMengundi->id, 404);
return view('admin.management.penempatan.pindah', [
'pusat' => $pusatMengundi,
'assignment' => $assignment->load(['application', 'position', 'saluranMengundi']),
'pusats' => PusatMengundi::query()->with('election')->orderBy('code')->get(),
'salurans' => SaluranMengundi::query()
->where('pusat_mengundi_id', $pusatMengundi->id)
->orderBy('number')
->get(),
]);
}
public function pindah(Request $request, PusatMengundi $pusatMengundi, StaffAssignment $assignment): RedirectResponse
{
abort_unless($assignment->pusat_mengundi_id === $pusatMengundi->id, 404);
$data = $request->validate([
'pusat_mengundi_id' => ['required', 'exists:pusat_mengundis,id'],
'saluran_mengundi_id' => ['nullable', 'exists:saluran_mengundis,id'],
]);
$newPusat = PusatMengundi::query()->findOrFail($data['pusat_mengundi_id']);
$assignment->update([
'pusat_mengundi_id' => $newPusat->id,
'saluran_mengundi_id' => $data['saluran_mengundi_id'] ?? null,
'assigned_by_user_id' => $request->user()->id,
'assigned_at' => now(),
]);
// Update linked application's pusat too
if ($assignment->application) {
$assignment->application->forceFill(['pusat_mengundi_id' => $newPusat->id])->save();
}
activity('admin_management')
->performedOn($assignment)
->causedBy($request->user())
->withProperties([
'from_pusat_id' => $pusatMengundi->id,
'to_pusat_id' => $newPusat->id,
])
->log('assignment_transferred_by_admin');
return redirect()->route('admin.management.penempatan.show', $newPusat)
->with('status', 'Petugas telah dipindahkan ke '.$newPusat->name.'.');
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers\Admin\Management;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Management\DeleteRepresentativeRequest;
use App\Http\Requests\Admin\Management\StoreRepresentativeRequest;
use App\Models\Election;
use App\Models\JkmRepresentative;
use App\Models\KkmRepresentative;
use App\Models\PoliceEscort;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use App\Services\Admin\Management\AdminRepresentativeService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class RepresentativeController extends Controller
{
public function index(): View
{
return view('admin.management.representatives.index', [
'policeEscorts' => PoliceEscort::query()->with(['election', 'pusatMengundi', 'saluranMengundi'])->orderBy('name')->paginate(10, ['*'], 'police_page'),
'kkmRepresentatives' => KkmRepresentative::query()->with(['election', 'pusatMengundi'])->orderBy('name')->paginate(10, ['*'], 'kkm_page'),
'jkmRepresentatives' => JkmRepresentative::query()->with(['election', 'pusatMengundi'])->orderBy('name')->paginate(10, ['*'], 'jkm_page'),
'elections' => Election::query()->orderByDesc('id')->get(),
'pusats' => PusatMengundi::query()->orderBy('name')->get(),
'salurans' => SaluranMengundi::query()->with('pusatMengundi')->orderBy('number')->get(),
]);
}
public function store(StoreRepresentativeRequest $request, AdminRepresentativeService $service): RedirectResponse
{
$service->store($request->user(), $request->validated());
return redirect()->route('admin.management.representatives.index')
->with('status', 'Wakil agensi telah disimpan.');
}
public function destroy(DeleteRepresentativeRequest $request, string $type, int $id, AdminRepresentativeService $service): RedirectResponse
{
$service->delete($this->resolveRepresentative($type, $id), $request->user(), $request->validated('note'));
return redirect()->route('admin.management.representatives.index')
->with('status', 'Wakil agensi telah dipadam.');
}
private function resolveRepresentative(string $type, int $id): Model
{
return match ($type) {
'police' => PoliceEscort::query()->findOrFail($id),
'kkm' => KkmRepresentative::query()->findOrFail($id),
'jkm' => JkmRepresentative::query()->findOrFail($id),
default => abort(404),
};
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\Admin\Management;
use App\Http\Concerns\SortsQuery;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Management\UpdateStaffAssignmentRequest;
use App\Models\Position;
use App\Models\PusatMengundi;
use App\Models\SaluranMengundi;
use App\Models\StaffAssignment;
use App\Services\Admin\Management\AdminAssignmentManagementService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class StaffAssignmentController extends Controller
{
use SortsQuery;
public function index(Request $request): View
{
$query = StaffAssignment::query()
->with(['application', 'user', 'position', 'pusatMengundi', 'saluranMengundi', 'reportsTo.user'])
->when($request->filled('pusat_mengundi_id'), fn ($q) => $q->where('pusat_mengundi_id', $request->integer('pusat_mengundi_id')))
->when($request->filled('position_id'), fn ($q) => $q->where('position_id', $request->integer('position_id')))
->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')));
return view('admin.management.assignments.index', [
'assignments' => $this->sortedQuery($query, ['status', 'assigned_at'], 'id', 'desc')
->paginate(15)
->withQueryString(),
'positions' => Position::query()->orderBy('sort_order')->orderBy('name')->get(),
'pusats' => PusatMengundi::query()->orderBy('name')->get(),
'filters' => $request->only(['pusat_mengundi_id', 'position_id', 'status']),
]);
}
public function edit(StaffAssignment $assignment): View
{
$ktmPosition = Position::query()->where('code', 'KTM')->first();
return view('admin.management.assignments.edit', [
'assignment' => $assignment->load(['application', 'user', 'election.settings', 'position', 'pusatMengundi', 'saluranMengundi', 'reportsTo.user', 'histories']),
'positions' => Position::query()->where('is_assignable', true)->orderBy('sort_order')->orderBy('name')->get(),
'pusats' => PusatMengundi::query()->with('election')->orderBy('name')->get(),
'salurans' => SaluranMengundi::query()->with('pusatMengundi')->orderBy('number')->get(),
'ktmAssignments' => $ktmPosition
? StaffAssignment::query()
->with(['user', 'pusatMengundi', 'saluranMengundi'])
->where('position_id', $ktmPosition->id)
->where('status', 'active')
->where('id', '!=', $assignment->id)
->whereNotNull('saluran_mengundi_id')
->orderByDesc('id')
->get()
: collect(),
]);
}
public function update(UpdateStaffAssignmentRequest $request, StaffAssignment $assignment, AdminAssignmentManagementService $service): RedirectResponse
{
$service->update($assignment, $request->user(), $request->validated());
return redirect()->route('admin.management.assignments.edit', $assignment)
->with('status', 'Tugasan staf telah dikemas kini.');
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Http\Controllers\Admin\Management;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Management\StoreReturnTransactionRequest;
use App\Http\Requests\Admin\Management\StoreTakenTransactionRequest;
use App\Http\Requests\Admin\Management\UpsertWheelchairAllocationRequest;
use App\Models\Election;
use App\Models\PusatMengundi;
use App\Models\WheelchairAllocation;
use App\Services\Admin\Management\WheelchairManagementService;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;
class WheelchairController extends Controller
{
public function index(WheelchairManagementService $service): View
{
$allocations = WheelchairAllocation::query()
->with(['election', 'pusatMengundi', 'transactions'])
->latest()
->paginate(15);
return view('admin.management.wheelchairs.index', [
'allocations' => $allocations,
'elections' => Election::query()->orderByDesc('id')->get(),
'pusats' => PusatMengundi::query()->orderBy('name')->get(),
'service' => $service,
]);
}
public function store(UpsertWheelchairAllocationRequest $request, WheelchairManagementService $service): RedirectResponse
{
$allocation = $service->upsertAllocation($request->user(), $request->validated());
return redirect()->route('admin.management.wheelchairs.show', $allocation)
->with('status', 'Peruntukan kerusi roda telah disimpan.');
}
public function show(WheelchairAllocation $allocation, WheelchairManagementService $service): View
{
return view('admin.management.wheelchairs.show', [
'allocation' => $allocation->load(['election', 'pusatMengundi', 'transactions.recordedBy']),
'elections' => Election::query()->orderByDesc('id')->get(),
'pusats' => PusatMengundi::query()->orderBy('name')->get(),
'service' => $service,
]);
}
public function update(UpsertWheelchairAllocationRequest $request, WheelchairAllocation $allocation, WheelchairManagementService $service): RedirectResponse
{
$data = [
...$request->validated(),
'election_id' => $allocation->election_id,
'pusat_mengundi_id' => $allocation->pusat_mengundi_id,
];
$service->upsertAllocation($request->user(), $data);
return redirect()->route('admin.management.wheelchairs.show', $allocation)
->with('status', 'Peruntukan kerusi roda telah dikemas kini.');
}
public function recordTaken(StoreTakenTransactionRequest $request, WheelchairAllocation $allocation, WheelchairManagementService $service): RedirectResponse
{
$service->recordTransaction($allocation, $request->user(), [
'transaction_type' => 'taken',
...$request->validated(),
]);
return redirect()->route('admin.management.wheelchairs.show', $allocation)
->with('status', 'Rekod ambil kerusi roda telah disimpan.');
}
public function recordReturn(StoreReturnTransactionRequest $request, WheelchairAllocation $allocation, WheelchairManagementService $service): RedirectResponse
{
$service->recordTransaction($allocation, $request->user(), [
'transaction_type' => 'returned',
...$request->validated(),
]);
return redirect()->route('admin.management.wheelchairs.show', $allocation)
->with('status', 'Rekod pulang kerusi roda telah disimpan.');
}
}