16 KiB
Proposed Database Design
Phase 2 Implementation Status
Status: Implemented on 2026-05-28.
Implemented in migration:
user_profileselectionselection_settingsbahagian_pilihanrayasdaerah_mengundispusat_mengundissaluran_mengundispositionsposition_quotasapplicationsapplication_documentsapplication_status_historiesstaff_assignmentsstaff_assignment_historiespolice_escortskkm_representativesjkm_representativeswheelchair_allocationswheelchair_transactionsattendancesbank_verificationsexport_logssystem_notes
Implemented models and relationships are available under app/Models.
Implementation notes:
- Public-facing identifiers use UUID columns.
- MySQL index names are explicitly shortened where Laravel's generated names would exceed MySQL's identifier limit.
- Complex business constraints such as "four KP per KTM" are intentionally enforced by future services rather than only by database constraints.
- A PPM plus KTM dual-role assignment is represented through two active rows in
staff_assignments.
Export Log Purge Columns (Fasa 15 — 2026-06-02)
Added via migration 2026_06_02_000001_add_purged_at_to_export_logs.php.
| Column | Type | Purpose |
|---|---|---|
purged_at |
nullable timestamp | Set when the export file is deleted by exports:purge command |
purged_by_user_id |
nullable FK → users | Reserved for future manual purge UI attribution |
Records with purged_at IS NOT NULL are skipped on subsequent purge runs.
Performance Indexes (Fasa 13 — 2026-06-02)
Added via migration 2026_06_02_000000_add_performance_indexes.php.
applications table
| Index name | Columns | Purpose |
|---|---|---|
app_election_status_idx |
(election_id, status) |
Dashboard count queries and admin list filtered by election |
app_ktm_position_idx |
(selected_ktm_assignment_id, requested_position_id) |
KtmVacancyService reserved-application count (called per KP/KPDP vacancy check) |
staff_assignments table
| Index name | Columns | Purpose |
|---|---|---|
sa_reports_to_pos_status_idx |
(reports_to_assignment_id, position_id, status) |
KtmVacancyService active-assignment count under a KTM (called per vacancy check) |
Existing indexes (Phase 2)
| Table | Columns | Name |
|---|---|---|
applications |
(election_id, ic_number) |
auto |
applications |
(election_id, pusat_mengundi_id, status) |
auto |
staff_assignments |
(election_id, pusat_mengundi_id, position_id) |
sa_election_pusat_position_idx |
staff_assignments |
(election_id, saluran_mengundi_id, position_id) |
sa_election_saluran_position_idx |
attendances |
(election_id, pusat_mengundi_id, status) |
att_election_pusat_status_idx |
attendances |
(election_id, staff_assignment_id) |
att_election_staff_unique (unique) |
Why bank_verifications was not indexed with election_id
bank_verifications does not have a direct election_id column — it joins through application_id (unique FK). All finance queries that need election filtering use whereHas('application', ...) subqueries. The existing status index and unique application_id index are sufficient for the current query patterns.
Design Goals
- Support one election or multiple election events over time.
- Keep public identifiers separate from internal database IDs.
- Support dual-role assignment, especially PPM also acting as KTM.
- Keep sensitive documents private and access-controlled.
- Preserve auditability through history tables and activity log.
- Support exports, finance verification, attendance, wheelchair tracking, and post-closing notes.
Naming Conventions
- Table names use Laravel plural snake_case where practical.
- Existing Malay domain terms are retained for clarity.
- Public UUID/ULID columns use names such as
public_uuid. - Status fields use lowercase snake_case values.
- Foreign keys use
foreignId()->constrained()where possible.
Core Identity Tables
users
Laravel default user authentication table with additions as needed.
Key fields:
idnameemailpasswordemail_verified_atremember_token- timestamps
- soft deletes if operationally useful
Notes:
- Roles are assigned through
spatie/laravel-permission. - Not every public applicant must become a login user unless later required.
user_profiles
Stores extended identity details for staff/admin users.
Key fields:
iduser_idic_numberphone_numberaddressbank_namebank_account_number- timestamps
Notes:
- Sensitive fields should be masked in views where full values are not required.
- IC uniqueness at profile level should be evaluated with election-specific application rules.
Election and Settings
elections
Represents each election event.
Key fields:
idpublic_uuidnamecodestatusstarts_atpolling_date- timestamps
- soft deletes
Suggested statuses:
draftactiveclosedarchived
election_settings
Stores event-specific operational settings.
Key fields:
idelection_idregistration_start_dateregistration_end_datepolling_dateis_registration_openis_registration_open_overridenullableis_attendance_active- timestamps
Notes:
- Registration open state is computed from dates and
is_registration_open. is_registration_open_overridecan be used later if the system needs to distinguish manual override from normal date-based availability.- Attendance can be toggled separately for rehearsal or polling day.
Election Hierarchy
bahagian_pilihanrayas
Key fields:
idelection_idcodename- timestamps
- soft deletes
Relationships:
- belongs to election.
- has many daerah mengundi.
daerah_mengundis
Key fields:
idelection_idbahagian_pilihanraya_idcodename- timestamps
- soft deletes
Relationships:
- belongs to election.
- belongs to bahagian pilihanraya.
- has many pusat mengundi.
pusat_mengundis
Key fields:
idelection_iddaerah_mengundi_idpublic_uuidcodenameaddressregistration_urlqr_code_path- timestamps
- soft deletes
Relationships:
- belongs to election.
- belongs to daerah mengundi.
- has many saluran mengundi.
- has many position quotas.
- has many staff assignments.
- has one wheelchair allocation.
Notes:
public_uuidis used by/pohon/{pusat_mengundi_uuid}.registration_urlcan be generated dynamically; storing it is optional. If stored, it must be refreshed if domain changes.
saluran_mengundis
Key fields:
idelection_idpusat_mengundi_idnumbernamevoter_count- timestamps
- soft deletes
Relationships:
- belongs to election.
- belongs to pusat mengundi.
- has many staff assignments.
Roles and Permissions
Managed by spatie/laravel-permission:
rolespermissionsmodel_has_rolesmodel_has_permissionsrole_has_permissions
Application roles:
- Admin
- Admin Kewangan
- PPM
- KTM
- Pemohon
Positions and Quotas
positions
Stores operational staff positions.
Key fields:
idcodenamescopeis_public_applyableis_assignableallows_dual_role- timestamps
- soft deletes
Suggested position codes:
PPMKTMKPKPDPPAPMPOLISKKMJKMCALON_SIMPANAN
Suggested scopes:
pusatsaluranexternal
position_quotas
Defines vacancy requirements by election, pusat, saluran, and role.
Key fields:
idelection_idpusat_mengundi_idsaluran_mengundi_idnullableposition_idquota- timestamps
- soft deletes
Notes:
- PPM, PAPM, KKM, JKM, and wheelchair-related staffing are usually Pusat scope.
- KTM, KP, KPDP, Police escort, and calon simpanan are usually Saluran scope.
- Use nullable
saluran_mengundi_idfor Pusat-level quota.
Applications
applications
Represents public, KTM-created, or Admin-keyed applicant/staff application record.
Key fields:
idelection_idpusat_mengundi_idselected_ktm_assignment_idnullableuser_idnullablepublic_uuidsourcestatusnameic_numberphone_numberemailaddressrequested_position_idapproved_position_idnullablebank_namebank_account_numbercreated_by_user_idnullableapproved_by_user_idnullableapproved_atnullablerejected_by_user_idnullablerejected_atnullablerejection_reasonnullable- timestamps
- soft deletes
Suggested source values:
publiccreated_by_ktmadmin_manual
Suggested status values are documented in docs/05-workflow-design.md.
Rules:
- IC uniqueness is enforced per election among active/non-cancelled records.
- Public self-registration is blocked if an active
created_by_ktmrecord exists for the same election and IC. - Admin may manually enter records after registration closes with required note.
- Phase 4 public submissions create
source = public,status = submitted, and a public UUID reference. - For KP and KPDP public applications,
selected_ktm_assignment_idstores the selected active KTM assignment if vacancy remains.
application_documents
Stores private uploaded documents.
Key fields:
idapplication_iddocument_typediskpathoriginal_namemime_typesizeuploaded_by_user_idnullable- timestamps
- soft deletes
Document types:
ic_documentbank_statementother
Rules:
- IC and bank statement documents are required before PPM approval.
- Documents are stored on private disk.
- Phase 4 public uploads store
ic_documentandbank_statementunder private local storage using the application public UUID path.
application_status_histories
Tracks application lifecycle status changes.
Key fields:
idapplication_idfrom_statusnullableto_statuschanged_by_user_idnullablenotenullablemetadataJSON nullable- timestamps
Staff Assignments
staff_assignments
Canonical table for approved staff placement.
Key fields:
idelection_idapplication_idnullableuser_idnullableposition_idpusat_mengundi_idsaluran_mengundi_idnullablereports_to_assignment_idnullablestatusassigned_by_user_idassigned_atsource- timestamps
- soft deletes
Suggested statuses:
activependingcancelledreplaced
Notes:
- Allows one person to hold multiple assignments, including PPM and KTM.
reports_to_assignment_idcan link KP/KPDP to the approved KTM assignment.- A KTM assignment to Saluran is created by PPM or Admin, not chosen by KTM.
- Unique constraints should prevent duplicate active position assignment to the same role/scope where business rules require.
staff_assignment_histories
Tracks assignment changes.
Key fields:
idstaff_assignment_idfrom_position_idnullableto_position_idnullablefrom_pusat_mengundi_idnullableto_pusat_mengundi_idnullablefrom_saluran_mengundi_idnullableto_saluran_mengundi_idnullablefrom_reports_to_assignment_idnullableto_reports_to_assignment_idnullablechanged_by_user_idnotenullable- timestamps
Representatives and External Operational Data
police_escorts
Key fields:
idelection_idpusat_mengundi_idsaluran_mengundi_idnullablenameic_numbernullablephone_numbernullableranknullablestationnullablenotesnullable- timestamps
- soft deletes
kkm_representatives
Key fields:
idelection_idpusat_mengundi_idnameic_numbernullablephone_numbernullableagencynullablenotesnullable- timestamps
- soft deletes
jkm_representatives
Key fields:
idelection_idpusat_mengundi_idnameic_numbernullablephone_numbernullableagencynullablenotesnullable- timestamps
- soft deletes
Wheelchair Management
wheelchair_allocations
Key fields:
idelection_idpusat_mengundi_idallocated_quantitynotesnullable- timestamps
wheelchair_transactions
Key fields:
idwheelchair_allocation_idtransaction_typequantitytaken_atnullabletaken_by_namenullablereturned_atnullablereturn_conditionnullablerecorded_by_user_idnotesnullable- timestamps
Suggested transaction types:
takenreturnedadjustment
Return condition values:
baikrosak
Attendance
attendances
Key fields:
idelection_idstaff_assignment_idpusat_mengundi_idsaluran_mengundi_idnullableposition_idstatuscheck_in_timenullablerecorded_by_user_idnotenullable- timestamps
Attendance statuses:
presentabsentnot_recorded
Notes:
- Store denormalized pusat/saluran/position references for simpler reporting.
- A unique key should prevent multiple active attendance rows for the same election and staff assignment.
Finance
bank_verifications
Key fields:
idapplication_idstatusverified_by_user_idnullableverified_atnullablefinance_notenullable- timestamps
Statuses:
pendingverifiedrejectedrequires_correction
Notes:
- Admin Kewangan can update this table only.
- Assignment and placement edits are outside Admin Kewangan scope.
- Phase 4 public submissions create an initial
pendingbank verification row.
Exports and Notes
export_logs
Key fields:
idelection_idnullablegenerated_by_user_idgenerated_atreport_typefilter_parametersJSON nullablefile_namedisknullablepathnullable- timestamps
Report types:
full_staff_liststaff_list_by_pusatstaff_list_by_rolemissing_document_listfinance_verification_listattendance_summaryattendance_detail_by_pusat
system_notes
Stores structured operational notes, especially post-registration changes.
Key fields:
idelection_idnoteable_typenoteable_idnote_typenotecreated_by_user_id- timestamps
Suggested note types:
post_registration_changeadmin_correctionfinance_notegeneral
Audit Log
activity_log
Managed by spatie/laravel-activitylog.
Required logged details:
- actor
- subject
- event name
- previous values
- new values
- timestamp
- note or reason where available
Key Constraints and Indexes
Planned indexes:
elections.public_uuidpusat_mengundis.public_uuidapplications.public_uuidapplications.election_id, ic_numberapplications.election_id, pusat_mengundi_id, statusstaff_assignments.election_id, pusat_mengundi_id, position_idstaff_assignments.election_id, saluran_mengundi_id, position_idbank_verifications.statusattendances.election_id, pusat_mengundi_id, statusexport_logs.generated_by_user_id, generated_at
Planned uniqueness:
- Unique active public UUIDs.
- One active PPM per Pusat Mengundi.
- One active KTM per Saluran Mengundi.
- One active KPDP per KTM or Saluran based on final rule implementation.
- Four active KP per KTM/team enforced by quota service, not only DB constraint.
- Application IC uniqueness per election for active public/admin records, with special handling for deleted/cancelled KTM-created records.
Documented Trade-Offs
staff_assignmentsis used instead of a single role column because the system must support dual-role assignment.- Domain-specific history tables are retained in addition to generic activity log because workflows need reportable status/assignment timelines.
- Representative tables are separate from applications because Police, KKM, and JKM records may not follow the public applicant workflow.
- Attendance denormalizes role and location references to make election-day reporting faster and more stable.
Kemas kini operasi Pusat Mengundi - 2026-05-31
saluran_mengundis.namedikekalkan sebagai nullable untuk keserasian rekod lama, tetapi tidak lagi diminta pada UI.- Semasa Pusat Mengundi baharu didaftarkan, Saluran bernombor
1..Ndijana secara automatik. position_quotasmenyimpanPPM,PAPM,JKMdanKKMpada skop Pusat sertaKTM,POLIS,KP,KPDPdanCALON_TAMBAHANpada skop Saluran.- Kod jawatan
CALON_SIMPANANdimigrasikan kepadaCALON_TAMBAHAN.