Files
prn2026/docs/decision-log.md
2026-06-03 08:51:22 +08:00

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\prn2026 workspace, 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-activitylog plus 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_logs row.

2026-05-28 - QR Code Package Choice

Decision:

  • Plan to use a Laravel-compatible QR code package, with simplesoftwareio/simple-qrcode as 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_open is 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 mysql CLI is not currently in PATH.

Reason:

  • The requirement specifies MySQL, but local CLI check failed.

Impact:

  • Phase 1 must confirm DB connection through Laravel .env and migrations.

2026-05-28 - Phase 1 Laravel Version Installed

Decision:

  • Installed laravel/laravel v13.8.0.
  • Installed laravel/framework v13.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 /register routes.
  • 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.
  • /register returns 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-permission v7.4.1
    • spatie/laravel-activitylog v5.0.0
    • simplesoftwareio/simple-qrcode v4.2.0
    • laravel/breeze v2.4.2
    • larastan/larastan v3.9.6
    • openspout/openspout v5.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/excel in Phase 1.
  • Installed openspout/openspout v5.7.1 as the compatible spreadsheet export foundation.

Reason:

  • Composer rejected maatwebsite/excel for this environment.
  • Latest resolvable maatwebsite/excel versions either do not support Laravel 13 or depend on phpoffice/phpspreadsheet versions 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/excel releases 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/core
    • bootstrap-icons
    • jquery
    • sweetalert2
    • tom-select
    • flatpickr
    • datatables.net-bs5
    • datatables.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.example is configured for MySQL.
  • Local .env remains on SQLite for Phase 1 verification.

Reason:

  • The PHP pdo_mysql extension 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_Lumpur through APP_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 .env database prn2026.
  • 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_quotas because it exceeded the 64-character identifier limit.

Impact:

  • Composite indexes use concise names such as pq_election_saluran_position_idx and sa_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 as replaced, create a staff_assignment_histories row, and assign the RBAC role PPM to the user.

Reason:

  • Dual-role support requires assignments to remain separate from RBAC roles.
  • RBAC controls application access, while staff_assignments controls 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}/berjaya for 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 RegistrationPeriodService to centralize open/closed registration checks.
  • Treat is_registration_open_override as 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 local disk under applications/{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_ktm records do not block future self-registration.

2026-05-29 - Phase 4 Bank Verification Initialization

Decision:

  • Create a pending bank_verifications row 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 ApplicationPolicy and PpmScopeService.
  • Scope PPM access by active staff_assignments where position is PPM, status is active, 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_assignments row and sets application status to assigned.
  • 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 approved holding 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 AssignmentVacancyService for pusat-level and saluran-level placement checks.
  • Reuse KtmVacancyService for 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 KtmScopeService to define KTM ownership from active staff_assignments with position KTM, a Saluran Mengundi, and status active.

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 applications rows with source = created_by_ktm, status = submitted, and requested position KP.
  • 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_assignments with reports_to_assignment_id pointing to the KTM assignment and status assigned on the application.
  • KTM can delete only unassigned created_by_ktm records 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_ktm status 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 use source = 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 catatan when 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_notes linked 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.status and finance_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 FinanceExportService and query/filter logic in FinanceVerificationQuery.

Reason:

  • The original preferred package maatwebsite/excel remains 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_logs row with user, timestamp, report type, filters, file name, disk, and path.

2026-05-29 - Phase 8 Sensitive Finance Display

Decision:

  • Add App\Support\SensitiveData masking 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_quantity as the maximum quantity available for a Pusat Mengundi.
  • Treat wheelchair_transactions rows 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_active is 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_assignments row.
  • 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_logs row 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_statement documents.

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 --days option 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 - days on the export_logs table, not generated_at, because created_at is always set by Eloquent and cannot be null.
  • Records with files already missing from disk are still marked purged_at to prevent repeated processing attempts.
  • purged_by_user_id is 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() and runInBackground() 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.php now contains a Schedule::command(...) entry.
  • php artisan schedule:run must be configured in the operating system cron (per existing deployment notes).
  • export_logs table has purged_at and purged_by_user_id columns via migration 2026_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_ktm applications 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}/statuspublic.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 ApplicationDocument model (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) on applications, and (election_id, pusat_mengundi_id, position_id) and (election_id, saluran_mengundi_id, position_id) on staff_assignments.
  • Added only genuinely missing indexes via a separate migration to avoid modifying the baseline Phase 2 migration.
  • Did not add (election_id, status) on bank_verifications because that table does not have an election_id column.

Reason:

  • KtmVacancyService is called on every public KP/KPDP application and on the form for KTM option display. Two queries inside it ($assignedCount and $reservedCount) had no composite coverage.
  • The admin application list query does not filter by election_id, so (election_id, status) on applications primarily 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 RequireAdminPostCloseNote middleware and apply it to the entire admin/management route 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_id in 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_note registered in bootstrap/app.php.
  • Applied to ['auth', 'role:Admin', 'admin.post_close_note'] in routes/web.php for the management group.
  • Read operations (GET) pass through without note check.
  • Missing note returns redirect-back with validation errors on note field.

2026-06-02 - Fasa 12-A: Admin Settings UI

Decision:

  • Add Admin Settings UI at GET/PATCH /admin/setup/settings/{election} for toggling is_attendance_active and is_registration_open_override.
  • Place the Settings screen within the existing admin/setup route group and include it in the _nav.blade.php as a warning-toned button.
  • Log all settings changes via activity('admin_settings') with before and after values.

Reason:

  • Both is_attendance_active and is_registration_open_override were 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, and UpdateElectionSettingsRequest added.
  • Route names: admin.setup.settings.edit, admin.setup.settings.update.
  • Accessibility: Admin role only; all other roles are forbidden.
  • is_registration_open_override exposes 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 code CALON_TAMBAHAN instead of the old CALON_SIMPANAN.
  • Fixed tests/Feature/AdminSetupTest.php to include required operational counts (saluran_count, etc.) when posting to admin.setup.pusat.store, and to use saluran number '3' (not '1') to avoid conflict with auto-generated salurans from PusatOperationalSetupService.

Reason:

  • The 2026-05-31 phase renamed CALON_SIMPANANCALON_TAMBAHAN via migration. The seeder was not updated, causing KeyError when accessing $positions['CALON_TAMBAHAN'].
  • The 2026-05-31 phase also made saluran_count required 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 --seed now 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..N dijana secara automatik semasa Pusat Mengundi dicipta.
  • Kuota PPM, PAPM, JKM dan KKM disimpan pada skop Pusat Mengundi.
  • Kuota KTM, POLIS, KP, KPDP dan CALON_TAMBAHAN disimpan pada setiap Saluran. Ini mengekalkan model operasi satu pasukan bagi setiap Saluran.
  • Istilah CALON_SIMPANAN ditukar kepada CALON_TAMBAHAN melalui 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.