1199 lines
42 KiB
Markdown
1199 lines
42 KiB
Markdown
# 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}/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 `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_SIMPANAN` → `CALON_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.
|