Skip to content

Codebase Summary

Project: PTX Channel Manager (ptx-cm)
Version: 3.0.5
Last Updated: 2026-06-05
Advanced Filters: ✅ Implemented v3.0.1
List Pages Optimization: ✅ Implemented v3.0.2 (shared use-paginated-list hook, stats/facets endpoints, server-side sort)
OTA Inbound Reservations: ✅ Implemented v3.0.5 (Trip.com OpenTravel push endpoint)


1. Monorepo Dependency Graph


2. Directory Structure & Metrics

DirectoryPurposeEst. LOCFiles
apps/apiNestJS Backend~34,700399 TS
apps/webNext.js Frontend~64,500409 TSX/TS
packages/databasePrisma + migrations + seeds/imports~6,20019 (excl. generated)
packages/typesShared types/enums~1,1007
packages/configShared tsconfig presets3 JSON
docsDocumentation~16,00029 MD
TotalAll source (excl. generated)~106,500830+

3. Backend Module Map

42 Feature Modules (apps/api/src/modules)

Auth & Users:

  • auth — JWT, login, refresh, password reset, rate limiting
  • users — User CRUD, account settings (locale, country, dateFormat)
  • roles — Role definitions, permission bitmasks (15 modules × 4 actions)
  • activity-logs — HTTP request logging, audit trail via middleware

Inventory & Operations:

  • properties — Property CRUD, timezone/currency, city/buildingName, manager assignment
  • room-types — Room inventory, base rates per property
  • room-mappings — OTA ↔ local room type mapping
  • suppliers — Supplier/room-owner management
  • supplier-room-allocations — M:N supplier ↔ room allocation
  • supplier-apartments — Supplier apartment hierarchy (Lark-sourced, MANUAL unmapped support)

OTA Integration:

  • ota-accounts — Encrypted credentials (AES-256-GCM)
  • ota-connections — Property ↔ OTA account links + 25 Lark metadata columns; (otaAccountId, otaPropertyId) unique key; propertyId nullable for unmapped listings
  • ota-listings — Source listing inventory CRUD (pricing, policies, specs imported from Lark); soft-delete (isActive); system is source of truth after import
  • ota-adapters — Factory + 5 adapters (Booking, Trip, Expedia, Agoda Hotel, Agoda Homes)
  • ota-rate-configs — Per-OTA rate formula configuration + formula engine
  • ota-status — OTA-specific status definitions (per-OTA status mapping)
  • ota-inbound — Trip.com inbound reservation push endpoint (OTA_HotelResRQ/Modify/Cancel XML dispatch), PAN redaction, durable logging, error mapping

Bookings & CRM:

  • bookings — Booking CRUD, upsertFromOta dedup, manual creation, 4-axis status, health computation
  • booking-status — Configurable BookingStatusDef definitions with department tracks (cs, payment, source, accounting), health weights, hard-delete
  • customers — Guest profiles, booking consolidation, link/unlink/merge operations
  • risks — Booking risk flagging, categorization (refund_heavy, customer_cancel, many_incidents), decision tracking (MANAGER+)

Availability & Rates:

  • availability — Calendar-based availability matrix, block/unblock date ranges, multi-room selection
  • rates — Base rate management per room type
  • rate-rules — Markup/discount/seasonal rule definitions
  • rate-plans — Rate plan configurations with adjustments
  • bulk-rates — Batch rate updates across properties/room types

Sync Engine:

  • sync-engine — Polling scheduler, job tracking, OTA polling/availability sync processors
  • sync-jobs — Async job tracking with status

Chat & Threading:

  • chat — Booking-linked conversations, message CRUD, composing
  • threads — Ops triage + lead/manager tier actions (override assignee, backup-tag, rescue-thread for cancelled bookings)

Support & System:

  • alerts — Overbooking detection & notifications (6 alert types)
  • dashboard — KPI metrics (occupancy, revenue, alerts, sync status)
  • settings — App config (sync intervals, notification toggles, health flag rules)
  • notifications — In-app notification inbox (bell icon, dropdown, full page, filters); actor attribution; user preferences (mute by type)
  • in-app-notification — Service: createForUsers, list, markRead, markAllRead; preference-aware fan-out; daily retention job (90-day prune)
  • notification-preferences — Per-user mute preferences; GET/PUT /api/v1/notifications/preferences endpoints
  • countries — Reference data for filtering
  • filter-views — Saved advanced filter views (CRUD). Owner-only mutate (403/404), company-scoped shared visibility (isShared → read-only). Filters revalidated server-side.
  • health — Liveness probes
  • prisma — Prisma client provider module

Advanced Filtering Infrastructure (apps/api/src/common/filtering)

DSL & Registry:

  • filter-condition.types.ts — FilterCondition DSL (?af= URL-encoded JSON). Max 20 conditions, 50 array values per condition. Operators: string (contains, is, isEmpty), number (eq, gt, gte, lt, lte), date (isBefore, isAfter, is), enum/entity (isAnyOf, isNoneOf). Day boundaries for timestamptz fields = Asia/Ho_Chi_Minh fixed (+07:00); plain-date fields (@db.Date) compare as dates only.
  • filter-operators.ts — Per-type operator map, validation.
  • filter-field-registry.types.ts — Module field registry format (whitelist + prismaPath, 1-level relations). Public field metadata via toPublicFilterFields() (no prismaPath leak).
  • module-filter-registries.ts — Global map of module → registry (NOT exported via barrel — runtime cycle avoidance). Entries: supplier-apartments, bookings, + future modules.
  • build-prisma-where.ts — Convert FilterCondition[] → Prisma where object; AND-merge semantics (never clobbers country scope).
  • parse-af-param.ts — Decode & validate ?af= param, enforce hard limits (20 conditions, 50 array values).

Integration Points:

  • applyAdvancedFilters(where, af, module) — Called in all list services after building base where (country scope, legacy filters).
  • GET /{module}/filter-fields — Public metadata endpoint; gated by module permissions.
  • ListXxxDto.af?: string — Optional advanced filters param on all list DTOs.

Common Patterns (apps/api/src/common)

Entity Code Resolution:

  • entity-code/ — @Global module. EntityCodeService.resolve(entity, idOrCode, scope?) converts route params (internal code OR UUID) to real UUID for DB queries. generateFor(entity, prefix) creates unique 6-hex codes (with 8-hex fallback on collision). Wired into properties, customers, bookings, supplier-apartments detail routes + suppliers (by supplierCode) and departments (company-scoped).

Guards (applied globally, opt-out via @Public):

  • JwtAuthGuard — JWT signature & expiry validation
  • PermissionsGuard — Module:action bitmask verification
  • CountryScopeGuard — Country filter injection from user context
  • ThrottlerGuard — Rate limiting (5/min login, 10/min refresh)

Decorators:

  • @Public() — Skip JwtAuthGuard
  • @RequirePermission(module, action) — Bitwise permission check
  • @CountryScope() — Inject countryScope from user.country

4. Frontend Route Map

RouteLayerAuthPurpose
/login, /forgot-password, /reset-password, /change-password(auth)PublicAuth flows
/dashboard(dashboard)RequiredKPI overview
/bookings, /bookings/[id], /bookings/new(dashboard)RequiredBooking list, detail & manual creation
/properties, /properties/[id](dashboard)RequiredProperty list & detail
/customers, /customers/[id](dashboard)RequiredGuest CRM profiles
/ota-accounts, /ota-accounts/connect(dashboard)RequiredOTA account management
/ota-properties(dashboard)RequiredLark OTA listings data grid with KPI, filters, inline-edit
/ota-listings, /ota-listings/[id], /ota-listings/new(dashboard)RequiredSource listing CRUD (pricing, policies, specs)
/availability(dashboard)RequiredAvailability calendar & blocking
/rates, /rates/base-rates(dashboard)RequiredRate management (OTA formulas)
/rates/bulk-ops, /rates/bulk-apply(dashboard)RequiredBulk rate operations
/workflows(dashboard)RequiredWorkflow hub (status CRUD, health flags)
/workflows/designer(dashboard)RequiredVisual workflow diagram
/workflows/instances, /workflows/process-types(dashboard)RequiredProcess tracking
/workflows/trigger-rules, /workflows/visibility(dashboard)RequiredLifecycle rules & UI visibility matrix
/process-instances(dashboard)RequiredBPM workflow instances
/suppliers, /suppliers/[id](dashboard)RequiredSupplier management
/alerts, /sync-jobs, /logs(dashboard)RequiredMonitoring & audit
/master-data, /settings, /profile(dashboard)RequiredConfiguration

Context Provider Chain

AuthProvider → CountryProvider → ReferenceDataProvider → ThemeProvider → I18nProvider → ActivityTrackerProvider

Key Components (by directory)

  • availability/ — Availability grid, calendar picker, block-range popover, multi-room drag selection
  • rates/ — Formula overview matrix, bulk rate operations, bulk apply formula, price calendar
  • customers/ — Customer table, detail view, booking history merge
  • bookings/ — Booking detail (health badge, department status bar, 4-axis status track panel, cost/margin), history timeline, status transition dialog
  • properties/ — Property detail tabs, OTA connections, grouped sidebar list, pricing matrix
  • workflows/ — Health flags manager, transition editor panel
  • settings/ — Workflow status CRUD, visual diagram, transition table, UI visibility
  • chat/ — Chat conversation list, message thread, composer, WebSocket hooks, floating chat launcher (FAB + drawer)
  • layout/ — Sidebar navigation (supports collapsible NavGroup with inline submenu + localStorage-persisted expand state; Organization group has Overview/Companies/Users/Departments children), topbar, country switcher, language switcher
  • ui/ — Reusable primitives: data-table, dialog, tabs, status-badge, icon-picker, drawer (Radix Dialog-based right-anchored panel), etc.
    • filter-builder/ — Advanced filter UI: FilterBuilder button+panel, condition rows (type-switched operators & value inputs), field picker dropdown, entity multi-select, saved views picker. Hooks: useAdvancedFilters (encode/decode af param), useFilterFields (fetch field registry), useFilterViews (CRUD saved views). Wired into supplier-apartments & bookings pages. i18n: filters.* namespace (en, vi).

Route Builders (lib/routes.ts)

  • Typed builders for entity detail URLs: routes.property({internalCode}), routes.customer({internalCode}), routes.booking({internalCode}), routes.supplierApartment({internalCode}), routes.supplier({supplierCode}), routes.department({code}).
  • Enforce compile-time safety (tsc prevents bare strings or UUID interpolation).
  • Old UUID URLs keep working via backend route-param resolution; new canonical URLs use internal codes.

5. Database Schema (32 Models, 9 Enums)

Auth/Users (6): users, roles, refresh_tokens, password_reset_tokens, countries, activity_logs

Inventory (4): properties, room_types, suppliers, supplier_room_allocations

OTA Integration (5): ota_accounts, ota_connections, ota_listing_details, ota_room_mappings, sync_jobs

Bookings & CRM (5): bookings, booking_status_def, availability, customers, ota_status_def

Rates (7): rates, rate_rules, rate_plans, rate_plan_adjustments, ota_formula_templates, ota_rate_configs, ota_rate_lines

Operations & Filtering (5): alerts, audit_logs, settings, property_formula_configs, filter_views

Advanced Filtering (1):

  • filter_views — Saved filter configurations. Fields: module (supplier-apartments|bookings|...), name, filters (JSON: FilterCondition[]), isShared (company-scoped read-only visibility), createdBy (User), company (Company). Index on (companyId, module).

Key Relationships:

  • Property → [RoomTypes, OtaConnections, Availability, Manager(User)]
  • OtaAccount → OtaConnections → Bookings
  • RoomType → [Rates, RateRules, OtaRateConfigs, SupplierRoomAllocations]
  • Booking → BookingStatusDef (4-axis: status, paymentStatus, sourceStatus, accountingStatus)
  • OtaFormulaTemplate → OtaRateConfig → OtaRateLines (rate formulas)

6. Tech Stack

LayerStack
FrontendNext.js 16, React 18, Tailwind CSS, TanStack Table, SWR, react-hook-form, zod
BackendNestJS 10, Passport JWT, class-validator, BullMQ, Redis
DatabasePostgreSQL 16, Prisma 7 ORM
AuthJWT + refresh tokens, HttpOnly cookies, bcrypt
EncryptionAES-256-GCM (OTA credentials)
BuildTurborepo, pnpm workspaces, TypeScript 5.7
Linting & FormattingBiome 2.4.16 (single quote, lineWidth 100)
TestingJest, @nestjs/testing

7. Key Design Patterns

Authentication: JWT payload (sub, email, roleId, permissions, country, locale) extracted from HttpOnly access_token cookie with Bearer fallback.

Authorization: Bitmask permissions per module (VIEW=1, CREATE=2, EDIT=4, DELETE=8), 6 role presets (super_admin, admin, manager, ota, cs, fin).

OTA Adapters: Factory pattern returning strategy-based adapters for Booking, Trip.com, Expedia, Agoda (Hotel), and Agoda (Homes).

Sync Pipeline: BullMQ repeating jobs (150s) → BookingPull → AvailabilityCalc → OTA push, with SyncJob tracking for audit/retry.

Rate Engine: OTA-formula-based rate calculation. OtaFormulaTemplates define per-OTA price derivation logic. OtaRateConfigs link templates to properties. OtaRateLines store computed daily rates.

4-Axis Booking Workflow: Each booking tracks 4 department statuses (CS, Payment, Source, Accounting) via BookingStatusDef. Status transitions are role-gated with JSON-based transition rules. Health weights (ok/warning/risk) and flag rules drive bookingHealth computation.

Data Fetching: SWR for frontend caching, Context providers for global state, TanStack Table for CRUD tables.

List Page Pagination: Shared use-paginated-list hook (frontend) + server-side GET /{entity}/stats, GET /{entity}/facets, and sort whitelist (backend). All list pages (ota-properties, properties, suppliers, customers, supplier-apartments) unified onto this stack. Replaces 2–3.5MB fetch-alls with ~50KB paginated responses; preserves previous page during refresh via keepPreviousData.


8. Critical Files

FilePurpose
packages/database/prisma/schema.prismaSource of truth for DB schema
packages/types/src/enums.tsOTA types, role enums, status constants
docs/API_SPEC.mdBackend endpoint contracts
docs/DB_DESIGN.mdDatabase design rationale & ER
docs/BPM_SPEC.md⚠️ HISTORICAL — BPM architecture abandoned (E-21/E-22 removed 2026-04-19)

9. Sync Engine Flow (Core Feature)

PollingScheduler (startup) → Creates BullMQ repeating jobs every 150s

OtaPollingProcessor (concurrency: 3) → BookingPullService.pullBookings()

OTA Adapters (factory) → fetchBookings() from each OTA

BookingsService.upsertFromOta() → Dedup & upsert bookings

BookingHooks → Post-upsert side effects (notifications, status checks)

AvailabilitySyncProcessor (concurrency: 2) → AvailabilityCalcService.recalculate()

Count bookings per date, detect isOverbooked

AlertsService (if overbooking) + OTA Adapters.pushAvailability() (always)

Last Updated: 2026-06-05 | Status: Active

PTX Channel Manager — Internal Documentation