OViq Hotel ERP — Phase 10 localization development build

THIS IS NOT A PRODUCTION CERTIFICATION OR THE COMPLETE ORIGINAL MODULE MATRIX.
Read RELEASE-STATUS.txt for feature limitations and acceptance gates.

1. REQUIREMENTS
PHP >=8.2, PDO MySQL, mbstring, OpenSSL and Argon2id support.
MySQL 8.0+, transactional InnoDB, strict SQL mode. PHP-FPM and HTTPS for production.
The executed runtime was PHP 8.3.6 / MySQL 8.0.46 / Chromium.
PHP 8.2 itself, Safari, Firefox and Windows hosting were not exercised.
Bootstrap 5.3.3 is vendored locally, with its MIT license included.
No Composer install or external CDN is needed for the shipped UI.

2. FRESH INSTALL
Use a new EMPTY database. Set the web document root ONLY to public/.
Restrict /installer/ to your operator IP or HTTP authentication during setup.
Allow the PHP process to write storage/ and TEMPORARILY config/.
Open /installer/ and complete the wizard (admin password >=12 characters).
After success, remove config write permission and deny /installer/ at the server.
Keep storage/installed.lock. Do not expose app/, core/, tests/, config/ or bin/.
Do not use PHP's development server for live operations.

3. UPGRADE FROM PHASES 1–5
Back up the full database, config/, storage/uploads/ and installed.lock first.
Review deployment/preflight.sql for legacy-data inconsistencies.
Put the property application in maintenance mode and stop background writers.
Deploy code WITHOUT overwriting local config files, uploads or installed.lock.
From project root run: php bin/migrate.php
MySQL DDL auto-commits. The metadata-aware runner supports retry; reversal needs
restoring the pre-upgrade database AND matching code/config release together.
For old globally shared inventory items, automatic ownership is assigned only
when existing stock belongs to exactly one property. Remaining NULL owners are
quarantined from normal UI and require explicit operator reconciliation.
Do not assign ambiguous or shared inventory to an arbitrary property.
Historical room charges have no reliable business date in older builds.
Reconcile their business_date manually from verified source records; never infer
it from posted_at alone and never delete financial history to pass a preflight.
Reconcile any pre-upgrade POS group/receipt or inventory valuation differences.

4. DBA AUDIT GUARDS AND RUNTIME PRIVILEGES — REQUIRED BEFORE LIVE USE
Run as the DBA (separate from the web runtime):
  php bin/install-audit-guards.php
Optional setup connection overrides: OVIQ_SETUP_HOST, OVIQ_SETUP_USER,
OVIQ_SETUP_PASS. Use secret management; do not commit actual credentials.
Binary-logged MySQL may require DBA privileges to create triggers. Do not grant
SUPER to the runtime account or weaken server controls just to finish setup.
The two triggers reject UPDATE/DELETE on audit_logs. They do not defend against
a privileged operator dropping/truncating tables or disabling protections.
Use a separate runtime identity with table-specific DML grants and only
SELECT/INSERT on audit_logs. See deployment/runtime-grants.sql.example.
Use external append-only audit replication for privileged-operator resilience.
Then run: php bin/preflight.php
Any BLOCK output is a deployment stopper, not an informational warning.
A passing preflight checks only its listed invariants, not general security.

5. BACKUP AND RESTORE
Install the MySQL client tools. Use an independent backup database identity.
Run: php bin/backup.php /secure-backups/oviq-before-upgrade.sql
Optional overrides: OVIQ_BACKUP_HOST, OVIQ_BACKUP_USER, OVIQ_BACKUP_PASS.
The utility refuses overwrites, uses no shell string, creates owner-only files,
keeps passwords out of process arguments, removes incomplete files and prints
a SHA-256 digest. It does not encrypt backups: encrypt and restrict them outside
public/, apply a retention policy and protect guest/passport/payment metadata.
For a restore drill, have the DBA create a NEW disposable database and load the
SQL there with the MySQL client. Match table/record counts, journal balances,
audit triggers and application acceptance tests before a production restore.
The supplied release verification includes an isolated actual restore drill.
Do not restore into an occupied live database without a maintenance plan.

6. SERVER CONFIGURATION
Adapt deployment/nginx.conf.example and deployment/php.ini.example.
Test syntax before reloading. Configure real HTTPS, secure cookies, log rotation,
least-privilege file ownership and monitoring. No web-server configuration in
this package has been deployed or certified on your infrastructure.
The UI still has inline JavaScript; a strict nonce-based CSP migration is pending.
Mutation endpoints accept HTML form-encoded/multipart payloads only, not JSON.
CSRF tokens are checked centrally and in action handlers.
Authenticated HTML/API content is network-only. Offline fallback is static;
no offline guest records, queued writes or automatic resubmission are supported.

7. TESTS
The *_test suffix safety check does NOT guarantee a database is disposable.
Use a fresh dedicated database/user with no access to real hotel data.
Load installer/schema.sql, installer/seed.sql, then install DBA audit guards.
Set OVIQ_TEST_DB, OVIQ_TEST_HOST, OVIQ_TEST_USER, OVIQ_TEST_PASS.
Run: php tests/integration.php
Run: php tests/hardening.php
The latter starts independent PHP worker processes for race tests and requires
proc_open, filesystem access to a temporary directory and MySQL connections.
See tests/verification.txt for exact executed results and coverage limitations.

8. CUTOVER CHECKLIST
Complete all BLOCK gates; confirm tested backup and rollback; rotate setup
credentials; disable installer; verify HTTPS/session cookies; obtain legal/tax
and accounting sign-off; train staff; run a staged pilot with test payments;
complete external integration sandbox testing separately; obtain explicit
business acceptance. Concurrency control uses a coarse property-row mutex:
correctness was race-tested, but high-load throughput remains unbenchmarked.

9. PHASE 7 UPGRADE AND IDENTITY SECURITY
Back up code, database and configuration, including any existing encryption key.
Run php bin/migrate.php, then run php bin/keygen.php AS THE PHP SERVICE ACCOUNT
if OVIQ_APP_KEY is not supplied securely through the server environment.
Keygen is no-overwrite: an existing key is left unchanged. Do not regenerate or
replace it while enrolled users exist. Never include config/secret.key in a
release ZIP, source-control commit, support transcript or downloadable attachment.
The file is owner-readable (0600); PHP-FPM must run as an identity that can read it.
Fresh installer provisions this file once. Remove config directory write access
after installation. If using an environment key, use base64 encoding of exactly
32 random bytes and ensure every worker receives the same key.
Run php bin/preflight.php. It now blocks missing keys, invalid legacy 2FA flags,
and cross-property custom role assignments as well as the older invariants.
No global session is silently grandfathered through upgrade: users sign in again.

Visit /security for local authenticator enrollment. Save the ten recovery codes
OFFLINE when shown; the UI shows them once and the database retains only hashes.
Keep server time synchronized. Codes use SHA-1, six digits and 30-second periods
with one-step drift tolerance. A verified counter is never accepted a second time.
Enable property-required enrollment only AFTER the operator's own authenticator
has been verified and recovery codes safely stored. Unenrolled staff are confined
to the account-security enrollment screen until they enroll.

Visit /admin/access for property user and custom-role management. Every write
requires current administrator password; enrolled administrators also supply a
fresh TOTP or unused recovery code. Updating users or role grants revokes affected
sessions. System roles are read-only templates, and administrators cannot grant
permissions they do not hold or reassign/disable their own account.

OPERATOR EMERGENCY RECOVERY (DBA/OS CONTROL REQUIRED)
First verify the account holder independently and record an incident ticket.
Provide OVIQ_CONFIRM_ACCOUNT_RECOVERY matching the user ID and a new random
12–256 character password in OVIQ_RECOVERY_PASSWORD, using a secure secret manager.
Optional setup DB overrides: OVIQ_SETUP_HOST / OVIQ_SETUP_USER / OVIQ_SETUP_PASS.
Then run php bin/recover-account.php USER_ID "incident ticket and reason".
The CLI refuses unconfirmed use, rotates the password, clears authenticator and
recovery-code records, revokes sessions and records an audit event. Property-wide
2FA requirement stays active, forcing re-enrollment after the replacement password.
Do not leave environment secrets in shell history; unset them after the procedure.
Do not expose the CLI under public/ and do not automatically disable MFA when
an authenticator/key fails to decrypt.

ADDITIONAL TESTS
Run php tests/identity.php and php tests/identity_races.php against a dedicated
*_test database. They use a per-run environment encryption key and independent
worker processes. Never point test suites at real hotel data.


PHASE 8 UPGRADE / HR ACCEPTANCE
1. Take a database backup and perform a restore drill. Preserve config/database.php, config/secret.key (or external OVIQ_APP_KEY), storage/uploads and storage/installed.lock. Deploy the updated tree with public/ as document root.
2. Put the application in maintenance mode and run `php bin/migrate.php`. The runner now applies phase8 after phase4–phase7 and uses schema metadata to resume added columns/indexes. MySQL DDL auto-commits; backups and maintenance mode are required. Do not run the raw migration twice.
3. Existing DBA audit guards remain installed. New installs still require the documented DBA-only `php bin/install-audit-guards.php` before readiness approval. Do not grant SUPER to the application account.
4. Run `php bin/preflight.php`. Shift ownership is inferred only when all legacy roster uses belong to one property; ambiguous/unreferenced legacy shifts stay NULL and are inaccessible to normal shift administration. A DBA must inventory, clone/reassign per-property templates where needed, and remove/archive unresolved global templates once unused. Never guess shared ownership.
5. Backfill/verify employee names and employment end dates through HR. Legacy roster rows require a valid property-owned shift and explicit re-save before payroll use. Existing attendance is marked pending, not silently approved. Verify whether historical timestamps really are UTC; migration does not guess/convert undocumented legacy times. Re-save corrected local times before approval.
6. Historical approved/paid slips without snapshot/source/currency remain read-only unverified legacy records; they cannot be reapproved or marked paid. Preflight blocks readiness until the DBA/payroll owner reconciles their provenance with retained evidence. Do not manufacture historical snapshots. No supported reversal/reissue workflow exists yet.
7. Grant hr.manage for directory/shifts/attendance; grant hr.payroll for compensation/payroll access. The existing HR Officer template includes both. No role grants were widened by the migration. Salary updates require both HR management access and payroll permission.
8. Register staff, configure compensation, define shifts and assign roster dates. Record/approve attendance; generate and review the monthly draft. Resolve exceptions and regenerate stale drafts. Approve only a completed month with current password and, when enrolled, an unused authenticator/recovery code. A consumed factor must be replaced by a fresh factor even when retrying a failed operation.
9. Record only an externally completed salary payment with its reference. The application does not send money or post salary expense/payable GL entries. Print the frozen payslip or choose Save as PDF in the browser; there is no server-side PDF mailer.
10. Every approval freezes new/edited/deleted rosters and attendance for that employee's work-date month. Compensation changes apply to future generation and never rewrite historical slips. There is no approved-payroll correction path yet; pilot before real use.

PAYROLL FORMULA / POLICY v1
- Monthly base * calendar employment days / calendar days in month, half-up to cents.
- Approved net attendance minutes minus snapshotted net roster minutes, floor at zero per work date; sum overtime minutes, multiply configured hourly rate / 60, half-up to cents once per month.
- Gross = prorated base + fixed allowance + overtime.
- Deduction = fixed deduction + configured percent of prorated base, rounded half-up; net cannot be negative.
- No automatic absence/leave deduction or jurisdictional statutory rules. Independent local payroll/tax review is required.

TESTING NOTE
Shipped PHP suites refuse databases whose names do not end in _test. tests/hr.php creates/drops one test-only audit-failure trigger to prove rollback and therefore needs an isolated TEST DBA connection when binary logging restricts trigger creation. This is a test prerequisite, not an application privilege. CI declares PHP 8.2/8.3; this release's local execution evidence is PHP 8.3 only unless verification.txt says otherwise.


PHASE 9 MULTI-PROPERTY UPGRADE / ACCEPTANCE
- Back up and restore-drill first. Preserve config/database.php, encryption key, storage/uploads and installed.lock. Deploy updated code with public/ as docroot; maintenance mode and php bin/migrate.php now apply phase9 after previous migrations. Keep DBA-installed audit guards and least-privilege database accounts.
- Phase9 adds property_memberships and property_requests only. Existing users keep their home access; migration does not backfill grants or change home, role, salary, guest or reservation ownership. Run php bin/preflight.php after migration; active custom-role additional memberships and active users without an active home require operator reconciliation.
- Sign in as a system Super Admin at its HOME property, open Properties & Access, confirm password/fresh enrolled factor and create a property. Its creator receives an explicit additional grant. Choose an IANA timezone, supported currency and reviewed local tax percentage. Defaults are 14:00 check-in and 11:00 checkout; subsequent property profile editing is not supplied yet.
- Create property-local staff with a non-Super-Admin system role through the central form; login home is the selected property. Configure rooms, outlets, inventory and employees independently using existing modules. No branch cloning or opening balances are copied.
- Additional access is granted/revoked explicitly to active home-owned users with system roles. The full existing role applies in each assigned branch. Verify intended permissions before granting; there are no per-branch role overrides. Custom local roles are ineligible. Ordinary Users & Roles administration is home-only; changing an assigned role revokes additional memberships and requires reapproval. Additional grant/revoke signs the affected user out everywhere.
- Select a property card to switch. Home account ownership is never rewritten. Selection regenerates session ID/token, clears transient flash/enrollment state and redirects to the property page. Refresh other tabs; their old native forms fail 419 and shared AJAX context fails 409. Same-session simultaneous property tabs are deliberately unsupported.
- A target requiring 2FA accepts only an enrolled, verified factor session. Enroll at home first. Any assigned active property requiring 2FA prevents disabling it, even from a different branch. Security policy controls modify the ACTIVE property, not the administrator home implicitly.
- Treat all system Super Admins as group-level provisioning authorities; they see active property metadata globally. Do not use one installation for unrelated mutually untrusted organizations. Operational access remains explicit; no Super Admin operational scope bypass is added.
- No live providers, transfers, FX, consolidated reports, interproperty settlement or statutory compliance were added. Test representative room, folio, POS, inventory, HR and reporting workflows at every intended property before acceptance. Raw DB role/membership edits are outside supported application workflows.


PHASE 10 LOCALIZATION UPGRADE / ACCEPTANCE
1. Back up and restore-drill first; preserve production configuration (including application URL/settings), encryption key, uploads and installed.lock. Deploy public/ as docroot, use maintenance mode and run php bin/migrate.php followed by php bin/preflight.php. Keep all earlier DBA audit guards and least-privilege access requirements.
2. Phase10 reuses existing users.locale. It only creates property_translations and translation_requests; existing locale values, user homes/roles, memberships, global translations, monetary balances and historical snapshots are preserved. Unsupported saved locale values visibly fall back to English; users can explicitly save a supported choice later.
3. Use the header language selector or sign-in selector: en/fr/sw/ar. A signed-in selection is saved to that user's account; guest selection is temporary for the guest session. Successful sign-in reloads the account preference. Changing language does not change active property or money/input formats. A finite return-route allowlist prevents external redirects.
4. At an assigned active property, users with settings.manage can open Translations, select the target language and search a registered key/source. Edit plain text while retaining every placeholder (e.g. :name). Confirm current password plus a fresh authenticator/recovery code if enrolled. A used factor cannot be reused for the next sensitive action. Review the resulting labels and switch to another property to confirm isolation.
5. Removing an override restores the built-in translation via an inactive, versioned tombstone. It does not reset history/version counters. A stale editor must refresh after another editor changes/removes/recreates text. Export JSON contains only active overrides for the current property. Bulk import and arbitrary new message keys are not supported.
6. Review old unscoped translations separately. Do not infer that one global row applies to all branches. Public login has no property context and intentionally ignores property overrides. Auth.* property overrides therefore do not customize anonymous sign-in.
7. Check English, French, Kiswahili and Arabic shared navigation, login/challenge, dashboard, translation screens, RTL menu opening/closing, mobile widths and printing. Most business screens/error strings are still English. Use qualified native-speaking hotel/payroll/accounting reviewers before terminology acceptance; this release does not claim linguistic/legal certification.
8. PWA static asset cache is versioned as phase10. Allow the new service worker to activate, refresh other tabs and confirm new styles. Translation administration, exports and authenticated pages are network-only/no-store. Do not cache private multilingual pages in a reverse proxy.
9. Display-only French dashboard number grouping does not change canonical currency/input decimals or ISO work dates. Do not paste localized comma-decimal amounts into the existing dot-decimal transaction forms. No FX, locale-specific tax/statutory logic, Arabic-number input parsing or date conversion was added.

TEST NOTE: existing browser harness sign-in locators were narrowed to form[action=login] because the page now contains a second language form. Assertions for authentication/security and earlier business behavior were retained. All suites run against dedicated *_test databases; audit fault injection requires isolated TEST DBA privileges, never production SUPER grants.
