42 KiB
Decision Log
This log records architecture decisions, package choices, database design trade-offs, security decisions, and assumptions.
2026-05-28 - Phase 0 Workspace Baseline
Decision:
- Treat current workspace as a clean/new project because no Laravel files or git repository are present.
Reason:
- Inspection found an empty
C:\Users\User\Aplikasi\prn2026workspace, no.git, and no project files.
Impact:
- Phase 1 should create a fresh Laravel application.
- No existing code removal is required.
2026-05-28 - Laravel Version Selection Approach
Decision:
- Use the latest stable Laravel version supported by Composer and PHP during Phase 1.
Reason:
- Local PHP CLI is 8.5.1 and Composer is 2.9.4, which should support modern Laravel versions.
- Actual package compatibility should be verified at install time.
Impact:
- Do not lock a specific Laravel major version in Phase 0.
- Record final installed Laravel version in this log during Phase 1.
2026-05-28 - Authentication Scaffolding Choice
Decision:
- Plan to use Laravel Breeze with Blade stack for authentication scaffolding.
Reason:
- It is simple, maintainable, first-party, and suitable for government-style internal systems.
- Blade aligns with the requested stack.
Impact:
- Phase 1 should install Breeze after Laravel scaffolding.
- Public Pemohon QR flow should be custom and should not rely on open public account registration unless later required.
2026-05-28 - Frontend Stack Choice
Decision:
- Use Bootstrap 5, Bootstrap Icons, Blade, Vite, and jQuery where suitable.
Reason:
- Bootstrap 5 provides responsive admin UI quickly and maintainably.
- Bootstrap Icons covers common UI icon needs.
- jQuery remains useful for simple progressive enhancements with plugins such as Select2/DataTables.
Impact:
- Avoid heavy SPA architecture.
- Keep Blade as the primary rendering model.
2026-05-28 - RBAC Package Choice
Decision:
- Use
spatie/laravel-permission.
Reason:
- Mature Laravel package for roles and permissions.
- Supports middleware, Blade directives, and permission caching.
Impact:
- Role and permission seeders are required.
- Record-level rules still require Laravel policies and services.
2026-05-28 - Audit Package Choice
Decision:
- Use
spatie/laravel-activitylogplus domain-specific history tables.
Reason:
- Activity log is useful for generic before/after auditing.
- Domain history tables are better for reporting application status and assignment changes.
Impact:
- Implement structured histories for application and assignment workflows.
- Use activity log for broad audit trail requirements.
2026-05-28 - Excel Package Choice
Decision:
- Use
maatwebsite/excel.
Reason:
- It is the common Laravel package for XLSX exports/imports and supports export classes.
Impact:
- Exports will live in
app/Exports. - Export query/filter logic will live in services.
- Every export must create an
export_logsrow.
2026-05-28 - QR Code Package Choice
Decision:
- Plan to use a Laravel-compatible QR code package, with
simplesoftwareio/simple-qrcodeas the first candidate if compatible.
Reason:
- The system requires QR codes for Pusat Mengundi public registration links.
Impact:
- Confirm package compatibility during Phase 1 before installation.
- If incompatible, choose another maintained Laravel-compatible QR generator and document the change.
2026-05-28 - UI Helper Package Choices
Decision:
- Plan to use SweetAlert2, Select2 or Tom Select, Flatpickr, and either DataTables or Laravel server-side pagination depending on screen needs.
Reason:
- SweetAlert2 improves confirmations.
- Searchable select controls are useful for Pusat, Saluran, KTM, and role assignment forms.
- Flatpickr improves date/time input consistency.
- Server-side pagination is safer for large operational data; DataTables may be used selectively.
Impact:
- Final npm package list must be verified in Phase 1.
- Prefer server-rendered pages and pagination for maintainability.
2026-05-28 - Optional Debug and PDF Packages
Decision:
- Do not install Laravel Telescope in the initial Phase 1 production baseline.
- Do not install DOMPDF in Phase 1.
Reason:
- Telescope is useful for local development but should not be part of the production default unless explicitly configured and protected.
- DOMPDF is only needed later if printable PDF reports become a confirmed requirement.
Impact:
- Reconsider Telescope only for local development after core modules exist.
- Reconsider DOMPDF only when a concrete PDF report format is provided.
2026-05-28 - Static Analysis and Formatting
Decision:
- Use Laravel Pint.
- Use Larastan/PHPStan if compatible with the selected Laravel and PHP versions.
Reason:
- Pint provides consistent code style.
- Static analysis improves reliability, but compatibility with PHP 8.5 and current Laravel must be checked.
Impact:
- Phase 1 or Phase 11 should configure quality commands.
2026-05-28 - Testing Framework
Decision:
- Use Laravel's built-in PHPUnit baseline.
- Pest may be installed if compatible and beneficial after Laravel setup.
Reason:
- PHPUnit is always available in standard Laravel projects.
- Pest improves readability but should not block delivery if compatibility issues arise.
Impact:
- Tests should be written in a way that can be implemented with PHPUnit even if Pest is skipped.
2026-05-28 - Dual-Role Assignment Model
Decision:
- Model operational roles through
staff_assignments, not a single role field on users or applications.
Reason:
- A PPM may also be KTM.
- Staff placement is tied to election, Pusat Mengundi, Saluran Mengundi, position, and team structure.
Impact:
- A person can have more than one active assignment when business rules allow it.
- Validation services must enforce invalid duplicates while allowing PPM + KTM.
2026-05-28 - Public Identifier Security
Decision:
- Use UUID or ULID public identifiers for public QR links.
Reason:
- Public URLs must not expose internal auto-increment IDs.
Impact:
- Pusat Mengundi requires a public UUID/ULID column.
- Public route binding should resolve by public identifier.
2026-05-28 - Sensitive Document Storage
Decision:
- Store IC documents and bank statements on private Laravel storage.
Reason:
- These are sensitive documents and must not be publicly accessible.
Impact:
- No public storage symlink for sensitive uploads.
- Downloads must go through authorized controller actions.
2026-05-28 - Registration Period Assumption
Assumption:
- Registration period is event-specific and stored in
election_settings. election_settings.is_registration_openis stored and used together with the configured registration dates.
Reason:
- The system may need to support future elections with different dates.
- The original requirement explicitly asks for a setting indicating whether registration is open.
Impact:
- Registration checks must use the active election setting.
- Admin settings UI must make both the date window and open/closed state clear.
2026-05-28 - Generic Public Account Registration
Assumption:
- Generic public user registration should not be enabled by default.
Reason:
- Requirement says registration is only for public applicant flow if allowed.
- QR applicant flow can collect application records without creating login accounts.
Impact:
- Breeze registration may be disabled or restricted for internal user creation.
- Admin-created users handle internal roles.
2026-05-28 - MySQL Availability
Assumption:
- MySQL server will be provided even though
mysqlCLI is not currently in PATH.
Reason:
- The requirement specifies MySQL, but local CLI check failed.
Impact:
- Phase 1 must confirm DB connection through Laravel
.envand migrations.
2026-05-28 - Phase 1 Laravel Version Installed
Decision:
- Installed
laravel/laravelv13.8.0. - Installed
laravel/frameworkv13.12.0.
Reason:
- Composer resolved Laravel 13 as the latest stable skeleton/framework compatible with PHP 8.5.1.
Impact:
- Package compatibility was verified against Laravel 13 and PHP 8.5.1 during installation.
2026-05-28 - Phase 1 Authentication and Registration
Decision:
- Installed Laravel Breeze v2.4.2 for simple Blade authentication scaffolding.
- Disabled generic public
/registerroutes. - Retained public applicant registration for a later QR-specific module.
Reason:
- Internal users should be Admin-created.
- The original requirement allows public registration only for the applicant flow if enabled.
Impact:
/login, password reset, logout, profile, and email verification routes exist./registerreturns 404.- Public applicant registration will be implemented under
/pohon/{pusat_mengundi_uuid}in Phase 4.
2026-05-28 - Phase 1 Package Compatibility Results
Decision:
- Installed compatible Composer packages:
spatie/laravel-permissionv7.4.1spatie/laravel-activitylogv5.0.0simplesoftwareio/simple-qrcodev4.2.0laravel/breezev2.4.2larastan/larastanv3.9.6openspout/openspoutv5.7.1
- Laravel Pint was already installed by the Laravel 13 skeleton at v1.29.1.
- PHPUnit was installed by the Laravel 13 skeleton at v12.5.28.
Reason:
- Composer successfully resolved these versions with Laravel 13.12.0 and PHP 8.5.1.
Impact:
- RBAC, activity log, QR generation, auth scaffolding, static analysis, formatting, and spreadsheet export foundations are available.
2026-05-28 - Maatwebsite Excel Compatibility Exception
Decision:
- Did not install
maatwebsite/excelin Phase 1. - Installed
openspout/openspoutv5.7.1 as the compatible spreadsheet export foundation.
Reason:
- Composer rejected
maatwebsite/excelfor this environment. - Latest resolvable
maatwebsite/excelversions either do not support Laravel 13 or depend onphpoffice/phpspreadsheetversions constrained to PHP<8.5.0. - The local environment is PHP 8.5.1.
- Faking Composer platform PHP was rejected because it would weaken production-readiness.
Impact:
- Export service design remains unchanged.
- Export implementation should use OpenSpout unless
maatwebsite/excelreleases a PHP 8.5 and Laravel 13 compatible version or the runtime target is changed to a compatible PHP version. - This is a documented deviation from the preferred export package in the original requirement due to compatibility.
2026-05-28 - Phase 1 Frontend Package Results
Decision:
- Installed npm packages:
bootstrap@popperjs/corebootstrap-iconsjquerysweetalert2tom-selectflatpickrdatatables.net-bs5datatables.net-responsive-bs5
Reason:
- These packages support the requested Bootstrap 5, jQuery-assisted, responsive admin UI.
Impact:
- Vite production build is available.
- Tailwind dependencies remain from Breeze scaffolding but are not used by the application layout.
2026-05-28 - Generated View Replacement
Decision:
- Replaced generated Laravel/Breeze Tailwind views with Bootstrap 5 Blade views.
Reason:
- The original requirement specifies Bootstrap 5.
Impact:
- Auth, guest, app, navigation, profile, welcome, and dashboard views now follow the Bootstrap-based government-style admin UI direction.
2026-05-28 - Phase 1 Local Database Choice
Decision:
.env.exampleis configured for MySQL.- Local
.envremains on SQLite for Phase 1 verification.
Reason:
- The PHP
pdo_mysqlextension is available, but MySQL CLI/server credentials are not available in the current workspace. - SQLite allows auth, RBAC migrations, seeding, and tests to be verified immediately.
Impact:
- Production and shared environments must configure MySQL in
.env. - Local development can switch to MySQL once credentials are provided.
Superseded by:
2026-05-28 - Phase 2 MySQL Connection Confirmed.
2026-05-28 - Application Timezone
Decision:
- Set application timezone to
Asia/Kuala_LumpurthroughAPP_TIMEZONE.
Reason:
- The system is for Malaysian election operations and polling-day attendance timestamps must use local time.
Impact:
- Date/time display, audit timestamps, and attendance check-in defaults will align with Malaysia time when using Laravel timezone helpers.
2026-05-28 - Phase 2 MySQL Connection Confirmed
Decision:
- Use MySQL for local Phase 2 migration and seeding verification.
Reason:
- Laravel successfully connected to MySQL 8.4.8 through PDO using
.envdatabaseprn2026. - This better matches the production database requirement than SQLite.
Impact:
- Phase 2 schema was verified against MySQL, including identifier length constraints.
- MySQL CLI remains unavailable in PATH, but Laravel/PDO connectivity is sufficient for migrations and tests requiring the configured app database.
2026-05-28 - Phase 2 Core Domain Migration Strategy
Decision:
- Implement Phase 2 domain schema in one ordered migration file.
Reason:
- The tables are tightly coupled by foreign keys and the phase is a single baseline domain cut.
- Keeping the initial domain schema in one migration makes fresh local setup easier at this stage.
Impact:
- Future schema changes should use separate additive migrations.
- Down migration disables foreign key checks to safely drop circular application/assignment references.
2026-05-28 - Phase 2 Index Name Strategy
Decision:
- Use explicit short names for long composite indexes.
Reason:
- MySQL rejected Laravel's generated index name for
position_quotasbecause it exceeded the 64-character identifier limit.
Impact:
- Composite indexes use concise names such as
pq_election_saluran_position_idxandsa_election_saluran_position_idx.
2026-05-28 - Phase 2 Seed Data Strategy
Decision:
- Seed a realistic but small sample election hierarchy and operational assignments.
Reason:
- Later UI phases need immediate data for dashboards, setup screens, and authorization checks.
Impact:
- Seed data includes one election, one Bahagian, one Daerah, one Pusat, three Saluran, quotas, sample users, and a PPM who also has a KTM assignment.
2026-05-29 - Phase 3 Admin Setup Scope Boundary
Decision:
- Complete Phase 3 as Admin setup for hierarchy CRUD, Jawatan CRUD, quota setup, PPM assignment, and QR generation.
- Keep KKM, JKM, and Police operational CRUD for Phase 7 Admin management.
Reason:
- The original phase sequence defines Phase 3 around election structure, PPM assignment, jawatan/quota setup, and QR links.
- KKM, JKM, and Police records need the broader Admin management controls, post-registration notes, and audit behavior planned in Phase 7.
Impact:
- The Phase 2 schema already contains representative tables.
- Phase 3 UI does not yet manage representative records.
2026-05-29 - Phase 3 QR Storage and Route Binding
Decision:
- Generate QR SVG files into private local storage under
qrcodes/pusat-mengundi/{public_uuid}.svg. - Serve the QR SVG through an Admin-protected controller route.
- Use
/pohon/{pusatMengundi:public_uuid}as the public registration URL.
Reason:
- The public URL must not expose internal database IDs.
- Keeping generated QR assets outside the public web root avoids accidental direct exposure and keeps access paths explicit.
Impact:
- Admin can regenerate QR codes per Pusat Mengundi.
- Public QR routes currently show a placeholder until Phase 4 implements the full application form.
2026-05-29 - Phase 3 PPM Assignment Service
Decision:
- Implement PPM assignment through
PpmAssignmentService. - Store the assignment in
staff_assignments, mark previous active PPM assignment for the same Pusat Mengundi asreplaced, create astaff_assignment_historiesrow, and assign the RBAC rolePPMto the user.
Reason:
- Dual-role support requires assignments to remain separate from RBAC roles.
- RBAC controls application access, while
staff_assignmentscontrols operational placement.
Impact:
- A user can be PPM and also hold another operational assignment such as KTM when later assignment rules allow it.
- PPM changes are auditable from the assignment history and activity log.
2026-05-29 - Phase 3 Quota Consistency Validation
Decision:
- Validate that a selected Saluran Mengundi belongs to the selected Pusat Mengundi when creating or updating a position quota.
Reason:
- Quotas must not accidentally attach a saluran from another Pusat Mengundi.
Impact:
- Position quotas can be set at pusat level by leaving saluran blank, or at saluran level with a matching pusat and saluran pair.
2026-05-29 - Phase 3 Public Layout
Decision:
- Add a lightweight public Blade layout for QR-facing pages.
Reason:
- Public QR routes should not render the authenticated internal navigation when accessed by unauthenticated applicants.
Impact:
- The Phase 3 QR placeholder is mobile-friendly and independent of internal Admin navigation.
2026-05-29 - Phase 4 Public Application Controller
Decision:
- Replace the Phase 3 QR placeholder route implementation with
PublicApplicationController. - Keep the public QR URL as
/pohon/{pusatMengundi:public_uuid}. - Use
/permohonan/{application:public_uuid}/berjayafor the post-submission confirmation page.
Reason:
- Existing QR codes and registration URLs must remain valid.
- Public UUID route binding avoids exposing internal auto-increment IDs.
Impact:
- Public applicants can now submit real applications through the QR link.
- The old placeholder controller remains unused and can be removed in a later cleanup with a recorded reason.
2026-05-29 - Phase 4 Registration Period Service
Decision:
- Add
RegistrationPeriodServiceto centralize open/closed registration checks. - Treat
is_registration_open_overrideas an explicit override when it is not null.
Reason:
- Public, KTM, PPM, and Admin workflows will all need consistent registration-period behavior in later phases.
Impact:
- Public GET and POST routes block submissions when registration is closed.
- Admin settings UI in a later phase should clearly show whether an override is active.
2026-05-29 - Phase 4 Public Upload Storage
Decision:
- Store public IC documents and bank statements on Laravel's private
localdisk underapplications/{application_public_uuid}. - Record metadata in
application_documents.
Reason:
- IC documents and bank statements are sensitive and must not be served from public storage.
Impact:
- Later document download/view actions must go through authorized controllers.
- Public success pages do not expose document paths.
2026-05-29 - Phase 4 KTM Vacancy Calculation
Decision:
- Add
KtmVacancyService. - For public KP/KPDP applications, remaining vacancy is quota minus active team assignments minus reserved submitted/under-review/approved applications for the same KTM assignment and role.
Reason:
- Applicants should only choose a KTM when vacancy exists, and pending submissions should reserve capacity to prevent overbooking.
Impact:
- The public form shows only KTM options with remaining vacancy for KP/KPDP.
- POST validation rechecks vacancy to prevent stale form submission.
2026-05-29 - Phase 4 Duplicate IC Rule
Decision:
- Normalize IC numbers to digits and enforce one active application per election.
- If the duplicate active record has
source = created_by_ktm, show a specific message instructing that KTM must delete that pre-created record first.
Reason:
- The original requirement explicitly distinguishes KTM-created records from public self-registration.
Impact:
- Public submissions are blocked for duplicate IC values.
- Cancelled or
deleted_by_ktmrecords do not block future self-registration.
2026-05-29 - Phase 4 Bank Verification Initialization
Decision:
- Create a pending
bank_verificationsrow immediately after public application submission.
Reason:
- This prepares records for the Admin Kewangan workflow and avoids missing finance state later.
Impact:
- Admin Kewangan module can later query pending bank checks without needing to backfill submitted public applications.
2026-05-29 - Phase 5 PPM Record-Level Authorization
Decision:
- Add
ApplicationPolicyandPpmScopeService. - Scope PPM access by active
staff_assignmentswhere position isPPM, status isactive, and assignment is at Pusat Mengundi level.
Reason:
- Role middleware alone cannot enforce "PPM only manages own Pusat Mengundi".
Impact:
- PPM can list, view, approve, reject, and download documents only for applications under their assigned Pusat Mengundi.
- Admin remains able to pass policy checks for future shared application views.
2026-05-29 - Phase 5 Approval Creates Assignment
Decision:
- PPM approval creates or updates an active
staff_assignmentsrow and sets application status toassigned. - Assignment history and application status history are written in the same transaction.
Reason:
- In this module, approval is coupled with the required placement selection for KTM, KP, KPDP, or PAPM.
- A separate
approvedholding state can still be used later if a workflow needs approval without placement.
Impact:
- Approved Phase 5 applications immediately appear as active staff assignments.
- Role changes before approval are recorded through application status metadata, assignment history, and activity log.
2026-05-29 - Phase 5 Vacancy Validation
Decision:
- Add
AssignmentVacancyServicefor pusat-level and saluran-level placement checks. - Reuse
KtmVacancyServicefor KP/KPDP assignment to KTM, with an option to ignore the application being approved when counting reserved vacancy.
Reason:
- PPM assignment must prevent overfilling KTM, KP, KPDP, and PAPM quotas.
- Pending KP/KPDP public applications reserve vacancy, but the current application should not block its own approval.
Impact:
- PPM can assign KTM only to an available Saluran.
- PPM can assign KP/KPDP only to an active KTM under the same Pusat Mengundi with remaining vacancy.
2026-05-29 - Phase 5 Authorized Document Download
Decision:
- Add PPM document download route under authenticated PPM review routes.
- Verify the document belongs to the application before streaming it from private storage.
Reason:
- PPM needs to review uploaded IC and bank statement documents, but files must remain private.
Impact:
- Uploaded files are not exposed through public storage URLs.
- Document viewing for Admin/Admin Kewangan can be added later using the same private-storage pattern.
2026-05-29 - Phase 5 Attendance Boundary
Decision:
- Do not implement polling-day attendance in Phase 5.
- Keep attendance screens, summaries, and exports for Phase 10.
Reason:
- The phase sequence separates PPM review/assignment from attendance reporting/export design.
- Attendance has separate dashboard and export requirements that should be implemented together.
Impact:
- PPM dashboard still shows attendance as a future module.
- Phase 10 remains responsible for polling-day attendance.
2026-05-29 - Phase 6 KTM Scope Service
Decision:
- Add
KtmScopeServiceto define KTM ownership from activestaff_assignmentswith positionKTM, a Saluran Mengundi, and statusactive.
Reason:
- A KTM may only manage KP records under their own assigned Saluran/team.
Impact:
- KTM listing, registration, approval, and deletion are constrained to the current user's active KTM assignments.
- KTM cannot manage another KTM's team.
2026-05-29 - Phase 6 KTM-Created Applicant Records
Decision:
- KTM registration creates
applicationsrows withsource = created_by_ktm,status = submitted, and requested positionKP. - The KTM form does not accept a selectable role; malicious extra role fields are ignored by the service.
Reason:
- KTM is only allowed to register KP, not KTM, KPDP, PAPM, PPM, Police, KKM, or JKM.
- The existing public duplicate-IC rule already blocks public self-registration while an active KTM-created record exists.
Impact:
- KTM-created applicants can be reviewed/approved by KTM under their own team.
- Public self-registration remains blocked until the KTM-created record is deleted or cancelled.
2026-05-29 - Phase 6 Email Notification
Decision:
- Add queued Markdown Mailable
KtmCreatedApplicantMail. - Queue the email when KTM creates a KP applicant.
Reason:
- The requirement asks that KTM-created applicants receive email notification with name, role, Pusat Mengundi, KTM name, and correction instructions.
Impact:
- Queue/mail configuration must be valid in production.
- Tests use Laravel mail fake to verify the mailable is queued.
2026-05-29 - Phase 6 KTM Approval and Deletion
Decision:
- KTM approval of KP creates active
staff_assignmentswithreports_to_assignment_idpointing to the KTM assignment and statusassignedon the application. - KTM can delete only unassigned
created_by_ktmrecords under their own assignment.
Reason:
- Approval is a placement action, and after registration closes only Admin should be allowed to register/edit/assign.
- Deletion is needed to unblock public self-registration for applicants pre-created by KTM.
Impact:
- KTM approval is blocked after registration closes.
- Deletion writes
deleted_by_ktmstatus history and soft deletes the application.
2026-05-29 - Phase 7 Admin Management Route Boundary
Decision:
- Add Admin management routes under
/admin/management, separate from/admin/setup.
Reason:
- Setup screens manage election structure and QR preparation, while Phase 7 manages operational application/staff corrections and representative records.
Impact:
- Admin dashboard now links to both setup and management.
- Admin Kewangan is blocked from management routes because placement changes are Admin-only.
2026-05-29 - Phase 7 Manual Entry Source
Decision:
- Admin manual applications use
source = admin_manual. - Direct Admin assignments use
source = admin_direct, while edited assignments usesource = admin_update.
Reason:
- Source flags distinguish public, KTM-created, PPM-approved, and Admin-maintained records for audit and later reporting.
- Admin-keyed records must not trigger KTM-created applicant bank/detail request email behavior.
Impact:
- Manual records create pending bank verification state but no email notification.
- Reports can filter records by operational source.
2026-05-29 - Phase 7 Post-Closing Catatan Enforcement
Decision:
- Centralize post-closing note enforcement in
AdminPostCloseNoteService. - Require
catatanwhen the election registration period is closed for Admin application edit, manual entry, direct assignment, assignment edit, and representative changes.
Reason:
- The original requirement makes post-registration Admin changes allowable only with a note.
Impact:
- Missing catatan returns validation errors.
- Accepted post-closing changes create
system_noteslinked to the changed record.
2026-05-29 - Phase 7 Representative Management
Decision:
- Implement Police, KKM, and JKM manual entry in one representative management screen while keeping their existing separate database tables.
Reason:
- Operationally these records are maintained together by Admin, but the schema separates fields because Police can be saluran-specific and has rank/station fields.
Impact:
- PPM representative display can continue reading the existing separate tables.
- Future edit forms can extend the same service without schema changes.
2026-05-29 - Phase 8 Finance Scope
Decision:
- Implement finance verification under
/kewangan/bank. - Limit Admin Kewangan write access to
bank_verifications.statusandfinance_note.
Reason:
- The finance role must verify bank details without changing placement or assignments.
Impact:
- Admin Kewangan can filter and update finance records.
- Admin Kewangan remains blocked from Admin management assignment routes.
2026-05-29 - Phase 8 Finance Export Implementation
Decision:
- Implement finance XLSX export with OpenSpout in
app/Exports/FinanceVerificationExport. - Keep export orchestration in
FinanceExportServiceand query/filter logic inFinanceVerificationQuery.
Reason:
- The original preferred package
maatwebsite/excelremains incompatible with this PHP 8.5/Laravel 13 environment. - The project already documented OpenSpout as the compatible export foundation.
Impact:
- Export files are stored in private local storage under
exports/finance. - Every finance export writes an
export_logsrow with user, timestamp, report type, filters, file name, disk, and path.
2026-05-29 - Phase 8 Sensitive Finance Display
Decision:
- Add
App\Support\SensitiveDatamasking helper and use it on finance screens for IC, phone, and bank account values.
Reason:
- Finance users need enough information to identify records, but full sensitive values should not be displayed unless necessary.
Impact:
- Finance list/detail screens mask IC, phone, and account numbers.
- Future modules can reuse the same helper for consistent sensitive data handling.
2026-05-29 - Phase 9 Wheelchair Management Placement
Decision:
- Implement wheelchair management under Admin Management at
/admin/management/wheelchairs.
Reason:
- Wheelchair allocation and movement are operational Admin functions tied to Pusat Mengundi, not initial election hierarchy setup.
Impact:
- Admin can access wheelchair management from the Admin management module.
- Admin Kewangan and other roles cannot access wheelchair management routes.
2026-05-29 - Phase 9 Wheelchair Quantity Rules
Decision:
- Treat
wheelchair_allocations.allocated_quantityas the maximum quantity available for a Pusat Mengundi. - Treat
wheelchair_transactionsrows as immutable movement records. - Calculate outstanding quantity as total taken minus total returned.
Reason:
- Separate movement records are more auditable than overwriting current state fields.
Impact:
- Taken transactions are blocked when quantity exceeds available allocation.
- Returned transactions are blocked when quantity exceeds outstanding quantity.
- Allocation cannot be reduced below outstanding quantity.
- Every allocation change and movement is written to activity log.
2026-05-29 - Phase 10 Attendance Activation
Decision:
- Block PPM attendance recording unless
election_settings.is_attendance_activeis true.
Reason:
- Attendance should only be used on polling day or when explicitly activated by Admin settings.
Impact:
- PPM can see attendance screens but cannot submit records while inactive.
- Tests explicitly activate the flag before successful recording.
2026-05-29 - Phase 10 Attendance Scope and Storage
Decision:
- Store attendance per active
staff_assignmentsrow. - Scope PPM recording by active PPM assignment to Pusat Mengundi.
Reason:
- Staff assignments already represent final placement and support dual roles.
- Attendance belongs to a concrete operational assignment, not only a user.
Impact:
- A user with dual role can have separate attendance records per assignment.
- Admin summaries can group attendance by Pusat, role, and Saluran.
2026-05-29 - Phase 10 Attendance Export
Decision:
- Implement attendance detail export using OpenSpout and
AttendanceExportService.
Reason:
- Report format may change later, so export formatting is isolated from controllers.
- OpenSpout remains the compatible XLSX writer for this environment.
Impact:
- Attendance detail exports are stored in private local storage under
exports/attendance. - Every export writes an
export_logsrow with report type and Pusat filter parameters.
2026-05-29 - Phase 11 Document Access Hardening
Decision:
- Add authorized document download routes for Admin and Admin Kewangan.
- Admin can download all application documents.
- Admin Kewangan can download only
bank_statementdocuments.
Reason:
- PPM document download existed, but Admin and Finance document access also needs an explicit private-storage controller path.
- Finance users should not download IC documents because the finance workflow only requires bank evidence.
Impact:
- Sensitive files remain on private storage and are served only through authenticated, role-protected routes.
- Document download actions are recorded in activity log.
2026-05-29 - Phase 11 Final Hardening Scope
Decision:
- Treat Phase 11 as final quality gate, security review, targeted test-gap closure, and documentation hardening.
Reason:
- The main feature phases are complete; remaining work should reduce release risk without changing broad behavior.
Impact:
- Added document authorization tests and sensitive-data masking unit test.
- Deployment notes now include final quality gate commands and document-storage warnings.
2026-06-02 - Fasa 15-A: Export File Purge Strategy
Decision:
- Retention period default: 30 days. Configurable via
--daysoption to allow shorter retention for audit events (e.g., polling day exports) or longer retention for post-event review. - Purge is triggered by
created_at < now - dayson theexport_logstable, notgenerated_at, becausecreated_atis always set by Eloquent and cannot be null. - Records with files already missing from disk are still marked
purged_atto prevent repeated processing attempts. purged_by_user_idis nullable and not set by the scheduled command (system-initiated purge). It is reserved for future manual purge UI where an Admin might trigger purge on demand and should be attributed.- The scheduled task uses
withoutOverlapping()andrunInBackground()so large purge runs don't block the scheduler.
Reason:
- Finance verification lists and attendance exports contain bank account numbers and personal data. Indefinite storage of these files increases breach risk.
- A 30-day default balances post-export review window (staff may need to re-download for a few weeks) against data minimisation.
Impact:
routes/console.phpnow contains aSchedule::command(...)entry.php artisan schedule:runmust be configured in the operating system cron (per existing deployment notes).export_logstable haspurged_atandpurged_by_user_idcolumns via migration2026_06_02_000001_add_purged_at_to_export_logs.php.
2026-06-02 - Fasa 14-A: Applicant Status Portal
Decision:
- Route uses a plain string parameter
{publicUuid}instead of route model binding{application:public_uuid}to prevent Laravel from returning 404 for unknown UUIDs. The controller handles all lookup and returns 200 with a generic message for invalid or internal-only UUIDs. deleted_by_ktmapplications and soft-deleted records return the same generic message as "not found", so the portal cannot be used to infer whether a UUID once existed.- Name masking shows only the first 3 characters to confirm identity to the applicant without revealing the full name to observers.
- IC number, bank account, and phone are never displayed.
- Rejection reason is displayed when status is
rejected— this is intentional as the applicant has a right to know why their application was rejected.
Reason:
- Without this feature, applicants could only find out their status by contacting the Admin or PPM, creating unnecessary support burden.
- UUID-as-identifier (not requiring login) balances accessibility with security — only the person who received the QR-generated UUID link can access the status.
Impact:
- Route:
GET /permohonan/{publicUuid}/status→public.applications.status. - No authentication middleware applied to this route.
SensitiveData::maskName()added.
2026-06-02 - Fasa 12-E: Document Type Allowlists
Decision:
- Define document type allowlists as constants on the
ApplicationDocumentmodel (ALLOWED_FOR_ADMIN,ALLOWED_FOR_PPM,ALLOWED_FOR_FINANCE), making the single source of truth co-located with the model they describe. - All download paths reference these constants; adding a new document type requires a deliberate update to the relevant allowlist.
Reason:
- Admin and PPM paths had no explicit type checks — silently allowed any document type. If a new sensitive type were added, those roles would automatically get access without a conscious decision.
- PPM path used 404 for ownership mismatch, which reveals document ID existence to unauthorised callers. Changed to 403.
- PPM path had no activity log — inconsistent with Admin and Finance paths.
Impact:
ApplicationDocument::ALLOWED_FOR_ADMIN = ['ic_document', 'bank_statement']— Admin can download both types.ApplicationDocument::ALLOWED_FOR_PPM = ['ic_document', 'bank_statement']— PPM can download both types (needed to review before approval).ApplicationDocument::ALLOWED_FOR_FINANCE = ['bank_statement']— Finance can only download bank statements.- Any new document type added to the system requires explicit inclusion in each allowlist before the relevant role can download it.
2026-06-02 - Fasa 13-A: Composite Database Indexes
Decision:
- Audit existing indexes before adding new ones. Verified that Phase 2 migration already contains
(election_id, ic_number)and(election_id, pusat_mengundi_id, status)onapplications, and(election_id, pusat_mengundi_id, position_id)and(election_id, saluran_mengundi_id, position_id)onstaff_assignments. - Added only genuinely missing indexes via a separate migration to avoid modifying the baseline Phase 2 migration.
- Did not add
(election_id, status)onbank_verificationsbecause that table does not have anelection_idcolumn.
Reason:
KtmVacancyServiceis called on every public KP/KPDP application and on the form for KTM option display. Two queries inside it ($assignedCountand$reservedCount) had no composite coverage.- The admin application list query does not filter by
election_id, so(election_id, status)onapplicationsprimarily helps dashboard count queries and future per-election reporting.
Impact:
- Migration:
2026_06_02_000000_add_performance_indexes.php. - Index verification steps documented in
docs/08-deployment-notes.md. - No model or service code changes required.
2026-06-02 - Fasa 12-C: Middleware RequireAdminPostCloseNote
Decision:
- Add
RequireAdminPostCloseNotemiddleware and apply it to the entireadmin/managementroute group as a safety net on top of the existing service-layer enforcement. - Keep all existing
$this->postCloseNoteService->requireIfClosed(...)calls in services (they provide the correct election context for each record). - Middleware resolves the election from:
election_idin the request body (create routes), or the first active election (edit/delete routes where the election comes from the model, not the request).
Reason:
- The existing service-layer enforcement is correct but convention-based — a new controller that forgets to call
requireIfClosed()would silently bypass the gate. - Middleware provides an automatic belt-and-suspenders check that applies to ALL write operations in the route group regardless of which service is called.
- The middleware approach is safe because: (a) single active election is the normal operating state, and (b) the service still does the precise check with the correct election.
Impact:
- Alias:
admin.post_close_noteregistered inbootstrap/app.php. - Applied to
['auth', 'role:Admin', 'admin.post_close_note']inroutes/web.phpfor the management group. - Read operations (GET) pass through without note check.
- Missing note returns redirect-back with validation errors on
notefield.
2026-06-02 - Fasa 12-A: Admin Settings UI
Decision:
- Add Admin Settings UI at
GET/PATCH /admin/setup/settings/{election}for togglingis_attendance_activeandis_registration_open_override. - Place the Settings screen within the existing
admin/setuproute group and include it in the_nav.blade.phpas a warning-toned button. - Log all settings changes via
activity('admin_settings')with before and after values.
Reason:
- Both
is_attendance_activeandis_registration_open_overridewere previously only editable via direct database access, which is unsafe in production. - The risk of accidental misconfiguration (e.g., forgetting to activate attendance on polling day) is high without a UI.
Impact:
ElectionSettingsController,ElectionSettingsService, andUpdateElectionSettingsRequestadded.- Route names:
admin.setup.settings.edit,admin.setup.settings.update. - Accessibility: Admin role only; all other roles are forbidden.
is_registration_open_overrideexposes three logical states: null (auto), true (force open), false (force closed), mapped via HTML radio buttons with empty string for null.
2026-06-02 - Fasa 12-D: Pembetulan SampleElectionSeeder (CALON_TAMBAHAN)
Decision:
- Fixed
SampleElectionSeeder::seedQuotas()to query position codeCALON_TAMBAHANinstead of the oldCALON_SIMPANAN. - Fixed
tests/Feature/AdminSetupTest.phpto include required operational counts (saluran_count, etc.) when posting toadmin.setup.pusat.store, and to use saluran number '3' (not '1') to avoid conflict with auto-generated salurans fromPusatOperationalSetupService.
Reason:
- The 2026-05-31 phase renamed
CALON_SIMPANAN→CALON_TAMBAHANvia migration. The seeder was not updated, causingKeyErrorwhen accessing$positions['CALON_TAMBAHAN']. - The 2026-05-31 phase also made
saluran_countrequired on POST for Pusat creation. The test was not updated to match, causing a conflict when it tried to manually create saluran number 1 that already existed.
Impact:
php artisan migrate:fresh --seednow runs cleanly.- All 90 tests pass.
- Pint and PHPStan pass.
2026-05-31 - Pendaftaran operasi semasa daftar Pusat Mengundi
- Nama Saluran tidak lagi diperlukan pada UI. Saluran dikenal pasti menggunakan nombor di bawah Pusat Mengundi.
- Borang daftar Pusat Mengundi menerima bilangan Saluran, KTM, Polis Iring, KP, PPM, KPDP, PAPM, JKM, KKM dan Calon Tambahan.
- Saluran bernombor
1..Ndijana secara automatik semasa Pusat Mengundi dicipta. - Kuota
PPM,PAPM,JKMdanKKMdisimpan pada skop Pusat Mengundi. - Kuota
KTM,POLIS,KP,KPDPdanCALON_TAMBAHANdisimpan pada setiap Saluran. Ini mengekalkan model operasi satu pasukan bagi setiap Saluran. - Istilah
CALON_SIMPANANditukar kepadaCALON_TAMBAHANmelalui migrasi data agar rekod sedia ada kekal boleh digunakan. - Penciptaan Pusat, Saluran dan kuota dilaksanakan dalam transaksi pangkalan data supaya konfigurasi tidak tertinggal separuh jika setup gagal.