Upgrade program — enterprise operations
The plan for bringing Alhuda's system to the standard described in this rulebook. Built from seven area reviews on 2026-09-17 (customer portal, partner portal, sales, groups & inventory, finance & approvals, visa/tickets/comms, admin & design system) plus the earlier lifecycle, inventory, visa, finance, children, airline and access-control audits.
Each item names the rules it satisfies. Status: ✅ done on a branch · 🔄 in progress · ⏳ queued · ❓ blocked on a decision.
Principles for every change
- Rule first — cite rule IDs; add PROPOSED/OPEN rules before building (README).
- Enforce in the database — money, data and access rules live in RLS, constraints and SECURITY DEFINER functions that take the actor from the session, never from the request (ACC-001).
- All-or-nothing — one business action = one database transaction (CXL-001).
- Recorded — database audit triggers on every business table; the reason given in a confirmation dialog is stored with the change (AUD-001).
- Confirm — every state-changing or money-moving action asks Yes/No, for every kind of user (UX-001).
- Role-based — each role lands on a home screen for its own work; menus and actions follow permissions, never role names (ACC-011/012).
- Smart — every screen shows what needs attention, the next best action and any inconsistency with the rest of the system; computed from records first, AI only for language, suggestions never act on their own (INT-001..007, file 17).
Branches so far (not merged, not deployed)
| Branch | Content | Status |
|---|---|---|
fix/lifecycle-hotfixes |
Finance rejection status, transfer/clone, capacity release, passenger-cancel recount, visa case creation, airline-cancellation approve permission | ✅ |
feat/audit-trail |
Append-only audit trail (73 tables), masked PII, journey timelines | ✅ (owner to confirm inclusion) |
docs/business-rules |
This rulebook | ✅ |
feat/cancellation-chain |
Released-seat register, per-seat airline cancellations (block + FIT), tickets/visa follow cancellation, ledger settlement by bank or supplier credit, group-invoice credit notes | ✅ |
fix/security-lockdown |
Part 1: privilege escalation, admin functions, open writes, secrets, finance PIN, WhatsApp, sign-up | ✅ |
Integration: integration/ops-upgrade combines all of the above plus Wave 1A–1D (274 migrations apply cleanly on Postgres 17; 1214 unit tests, 8 database suites with 231 checks and 23 edge-function checks pass; no new type errors — 2026-09-17). All following waves branch from it. Not deployed.
Wave 1 — stop leaks and protect money (P0)
1A Data isolation & portals on server functions ✅ (branch wave1/data-isolation)
- Read isolation on business tables: staff by
*.viewpermission; customers only their own records; partners only their agency's records (ACC-002, ACC-010). Take AGENT out ofis_staff_user(). - Close verified leaks: invoice by booking id,
/groupspayer contacts,/sales/customersand/sales/bookings/:idfor partners,/inventory/b2b-flightsbuyers and PNRs, full passport/Aadhaar/PAN in lists (AUD-020). - Portal actions as ownership-checked functions: customer submit payment / accept quotation / requests / profile; partner sign-up, booking, passengers, bed reserve, seat sale, invoices, requests (price from rate sheet, credit limit enforced, partner status checked).
- Revoke anonymous access to
try_increment_group_booked_count. - Stop matching portal logins to customers or partners by email.
1B Money integrity ✅ (branch wave1/money-integrity)
- Payments: always recorded pending; verifier ≠ recorder; posting approved in the same transaction; actor from session (FIN-032, ACC-020/021).
- Refunds: capped at paid minus previous refunds, maker-checker, credited from the original bank account, refund voucher (PRC-030, FIN-011).
- Posted vouchers immutable (trigger); remove ledger reset and GL force-delete (FIN-031, AUD-002).
- Block/FIT delete never hard-deletes finance rows; block purchase journals converted to INR; missing FX rate is an error (FIN-034).
- Gates: B2B cancellation approval, journal approval (
finance.journals.approve), rebuild/manual ledger (finance.ledger.rebuild), year close (finance.years.close). - Reports: aggregate in the database, one date (
entryDate), no 1,000-row truncation; group P&L includes group-invoice revenue and excludes cancelled passengers. - Remove
mark-paid; invoices change only by credit/debit notes (FIN-012).
1C Booking lifecycle enforcement ✅ (branch wave1/booking-lifecycle; PAX-002/003 defaults PROPOSED, configurable)
- Status transition function per LC-002; editing can't change status; creation starts DRAFT;
createdBystored; creator ≠ approver (LC-010). - Validate before writing; one atomic create; capacity checked on every path incl. group change and added passengers (INV-002).
- Passenger category and counts calculated on the server from DOB and service date (PAX-001…006) — needs PAX-002/003 decisions.
- Delete only DRAFT without payments; cancel any pre-travel booking through the cancellation flow (LC-020/021).
- Single booking-creation path for wizard, lead, quotation and partner.
1D Tickets, visa, communications ✅ (branch wave1/tickets-visa-comms)
- Ticket issue function: finance cleared, real ticket number, active passenger, actor from session; fix bulk name overwrite; maker-checker on exceptions (AIR §24).
- Visa status transitions enforced with
changedBy(VISA-003); readiness uses visa status per passenger (VISA-010); intake conversion carries all fields and matches customer (VISA-040); public intake captcha fail-closed + rate limit. - WhatsApp sending and integration tests move server-side; integration secrets become write-only (AUD-021).
- Bulk messages: preview, Yes/No, consent/opt-out (AUD-022).
Wave 1 follow-ups
F1 Finance controls hardening ✅ (branch fix/finance-controls) — the rest of audit findings 1, 3, 4, 9, 10, 16, 17, 28, 31, 34 plus the role re-seed:
- Journals: a browser write can only create a pending voucher and can never approve one; every reversal links to its original, follows its status, cannot be approved before it and can never exceed it; B2B cancellation vouchers post through a decision-backed function (FIN-031/032).
- Roles (owner decisions 2026-09-17): new SUPER_ADMIN — the only role with every permission — and CHARTERED_ACCOUNTANT; CEO/GM cut to a leadership bundle, IT_ADMIN to system settings, HR out of finance, cashier to receipts only, ops/sales managers out of journal approval and refund payout (ACC-011/012/021).
- Approval limits by amount as configuration, enforced for customer refunds and manual journals (ACC-030).
- Supplier transactions, TDS, financial years and finance configuration behind permissions; corrections after a reversal need finance.supplier_transactions.correct; supplier payments settle the supplier's own ledger; block overpayment guard fixed.
- One atomic counter per document series; opening balances become vouchers; reports aggregate in SQL by entryDate (IST) and can no longer be cut off at 1,000 rows; the missing permission checks on aging, receivables & payables, pending settlements, /finance/ledger, customer ledger, P&L summary and inventory P&L are in place.
- Receipts carry the day the money came in and a split receipt is one atomic call.
- Still open for F2–F4: the posting engine itself (advances and revenue at completion, FX, cancellation posting basis), ledger-derived balances, the Razorpay webhook, report functions for the remaining screens and the bank-reconciliation rebuild.
Wave 1P — platform reliability (runs alongside Wave 1) 🔄
- Type checking that actually runs (+ ratchet on the existing 41 errors), CI type check, database test job, edge-function checks, migration version guard, deploy runbook (PLT rules, file 15).
- Owner actions: move production to a paid Supabase plan with point-in-time restore; create a staging project; enable monitoring and error tracking.
Wave 2 — design system, role experience, journey and emergencies ⏳
First in this wave (owner priority):
- Universal journey (JRN-001..004): one computed stage per booking and traveller, shared labels/colours, readiness checklist. ✅ (branch wave2/journey-360) — journey and readiness computed in the database (20260919120000), shared src/lib/journey.ts + src/components/journey/*, journey header on the booking page, per-traveller journeys on the group Passengers tab, journey board /operations/journey, dashboard widgets for 2D. Tour-leader check-ins (FLD-002), emergency contacts (INC-005), mahram (PAX-022), vaccination/insurance (HLT-001/002) are reported as not recorded yet.
- Customer 360 (C360-001..003): Now · where they are today · itinerary · trips · travellers & family · documents · money · visa & tickets · requests & messages · health · activity. ✅ (branch wave2/journey-360) — /customers/:id, permission-aware sections; customer-portal parity (C360-002) is reusable server-side and ships in Wave 3.
- Duty of care (INC-001..020) 🔄 (branch wave2/duty-of-care, Wave 2C): incident module built — incidents with type/severity/status, travellers, owner and family liaison, communication log, private documents, recorded costs; checklist templates with the INC-010 death checklist in full (close blocked until every step is done or explicitly not applicable); traveller states hospitalised / missing / deceased (INC-012: deceased needs management and the typed incident number, never deletes data); incident board at /operations/incidents with severity columns, filters and the detail drawer; open-emergency banner on the booking and group screens and traveller flags on rooming and manifest lists; impact suggestions (INT-141); dashboard widgets for Wave 2D. New permissions incidents.view/manage/close/confirm_death. Response times are shown from the INC-003 PROPOSED defaults and Critical escalation is recorded, not sent — INC-003 (escalation chain, duty roster) and INC-011 (compassionate policy) still need management decisions; headcount/manifest removal and the finance effects of a death follow in Waves 3–4.
- ✅ Palette that passes WCAG AA in light and dark (burgundy primary, gold accent, one status colour map with icon + label) — see admin review §C. (branch
wave2/design-system: tokens insrc/index.css+tailwind.config.ts, hex mirrorsrc/lib/brandTokens.tsused by the print libraries.) - ✅ Shared components:
useConfirm(three levels: Yes/No; summary + reason for money/access/status; type-to-confirm for delete/void/grant) with the reason written to the audit trail;PageHeader,EmptyState, skeletons, error-with-retry,StatusBadgedriven by onestatusTokensmap (src/lib/statusTones.ts), money formatters (formatINR,formatINRCompact,amountInWordsINR). Confirmation coverage: ../operations/ux-001-coverage.md. ⏳ remaining: oneDataTablewith mobile cards. - Role dashboards (UX-010/011, owner requirement) ✅ (Wave 2D,
wave2/role-dashboards): every employee lands on a dashboard for their own job at/app, chosen from the permissions they hold (12 profiles: Leadership, Finance manager, Chartered accountant, Accountant, Cashier, Operations, Ticketing, Visa, B2B, Sales, Auditor, HR/IT; several jobs → a tab each, main first). Four sections per UX-010 with a deterministic daily brief (INT-160). Every number and list comes from a permission-checkeddash_*database function (supabase/migrations/20260919140000_role_dashboards.sql, testssupabase/tests/role_dashboards.sql), explains itself (rule id + facts, INT-004) and links to its records. Still open: journey (2B) and incident (2C) widgets plug intosrc/components/dashboard/widgets/registry.tsat integration; sales targets, lead owner/follow-up dates and approval limits (ACC-030) need data or decisions. - ✅ Navigation by job: Home · Sales · Operations · Ticketing · Visa · Finance · Partners & Suppliers · Reports · Admin; job-title chip; hide empty sections; 403 page. (one config array
src/components/layout/navConfig.ts; items by permission only.) - 🔄 Admin: real status column, multi-role editor, confirm + reason on every access change, atomic role/permission updates, audit viewer with filters, diff and export. (confirm + reason and permission gates done on the branch; multi-role editor, audit viewer and atomic updates outstanding.)
- ✅ Replace hard-coded role checks (
isAdmin, role lists) with permissions — in the screens touched by this branch (/adminroute, airline-block PNR visibility, permissions matrix);ProtectedRoute's staff/portal split stays.
Wave 3 — portals and field app ⏳
- Tour-leader app (FLD-001..005): assigned groups only, offline check-ins and headcounts, incident reporting, location shown on Customer 360. ✅ built on the web (
/field, branchfeat/field-app) and in the native app; incident reporting still not built. - The native app (TRV-001..012, FLD-006, 20-traveller-app.md) 🔄 (branch
feat/native-app,apps/mobile): one Expo app with the staff ERP, the tour leader's stack (live location of travellers who switched it on, the programme, notices) and the traveller's stack (my trip, the guide per trip type, the programme, passport scan that waits for review). The partner stack is not in the app; a partner is pointed to the web portal. -
Health & insurance (HLT-001..004): vaccination, insurance, special needs, readiness blocking.
-
Customer portal: Home (next trip, readiness checklist, dues), Trips → Overview · Travellers · Itinerary · Payments · Documents · Visa · Support; online payment (Razorpay); invoices, receipts, credit notes, refund tracker; cancellation request with policy preview; notifications; account security; mobile-first.
- Partner portal: onboarding workflow with approval; agency sub-users; catalogue at net rates; one booking flow with holds and deadlines; credit/wallet; statement of account; documents; after-sale requests; notifications.
Wave 4 — module workspaces ⏳
- Split
Finance.tsx(14k lines),Groups.tsx(8k),AirlineBlocks.tsx(4.8k),Bookings.tsx(3.4k) into routed workspaces. - Sales pipeline (owners, follow-ups, quotation expiry), unified approvals inbox, visa kanban + passport custody register, departure readiness board, rooming/seat boards with infant-guardian rules.
Intelligence — cross-cutting (every wave) 🔄
Each wave ships the signals for the screens it touches (file 17): Wave 1 — drift and consistency checks behind the new database functions (INT-130, INT-152 foundations); Wave 2 — readiness risk, next best action and daily brief on role homes and Customer 360 (INT-100, INT-103, INT-160), incident impact (INT-141); Wave 3 — live trip watch (INT-140), portal-facing reminders; Wave 4 — deadline radar, utilisation and margin warnings, receivables, bank matching, anomalies, cash-flow and tax/close assistant (INT-120..122, INT-150..154); Wave 5 — ask the system (INT-161), signal review (INT-030).
Finance audit (2026-09-17): five parallel audits — posting rules, finance ↔ operations connections, reports & reconciliation, controls & tax, finance UI/intelligence. Findings feed Wave 1B and a finance remediation plan.
Wave 5 — new capabilities ❓/⏳
-
After-trip experience (CX-001..005): feedback, complaints as cases, supplier scorecards, returning pilgrims.
-
Airline group lifecycle: request → versioned quotes → negotiation → approval chain → PNR → deposit schedule → inventory → holds → FOC → release wizard → ticketing → reconciliation (file 05).
- Deadline scheduler and alerts (T-7/3/1/0) for blocks, holds, payments, visas, name changes (AIR §21–22).
- Individual (FIT) seats to parity with block seats — done, INV-025. The counters, the single read, the unsold-seat write-off (FIN-037), the three missing finance events and the finance-event history are built, and the screens read the
GroupFlightlink rather than the legacyTravelGroup.fitId. What remains is schema, not routes, and is listed on the inventory API page: onepnrand onepricePerSeatper FIT row, so three tickets bought separately at their own fares can only be entered as N identical seats under the first PNR and a total price cannot be entered at all (₹1,98,122 over 3 seats is stored as 3 × ₹66,040.67); nolegs/returnLegs, so a multi-stop individual ticket loses its middle sectors; no partner (B2B) sale of a FIT seat; and no release penalty bands, so a FIT release is always free. - Tour lifecycle: automatic In Travel / Returned, closeout checklist, revenue recognition (LC-040/041, FIN-001).
- Tax: TCS s.394(1) on overseas packages, receipt vouchers on advances, advances liability, GSTR-1 at/atadj/cdnr/doc_issue (FIN-010/011/020) — ❓ needs CA answers FIN-002/003/010.
- Drift checker with approved repairs (AUD-010); live clean-up A/B/C.
- Payment schedules and reminders (PRC-010 ❓).
Decisions needed from management
The 22 OPEN rules across files 01–09 (age cut-offs PAX-002/003, bed/meal entitlements PAX-010, discount limits PRC-002, payment schedule PRC-010, approval limits ACC-030, data scope ACC-013, role catalogue ACC-011, visa rejection VISA-011, accounting standard and GST scheme FIN-002/003/010, and others). Wave 1 can proceed without them except PAX-002/003.