Tài Liệu Yêu Cầu Hệ Thống (SRD)
| Dự Án | PTX Channel Manager (ptx-cm) |
| Phiên Bản | 2.1.0 |
| Ngày | 2026-02-18 |
| Loại | Công cụ nội bộ (không SaaS) |
1. Tổng Quan Hệ Thống
1.1 Mục Đích
PTX-CM is an internal OTA extranet automation tool that prevents overbookings by auto-syncing room availability across multiple OTA partner dashboards (Booking.com, Agoda, Traveloka, Expedia) from a centralized interface. It targets a hospitality operator managing 100+ properties across Southeast Asia (Vietnam, Indonesia, Malaysia).
1.2 Vấn Đề
Staff manually update 4 OTA extranets per property (~400+ sessions). When a booking arrives on one OTA, availability isn't reduced on others fast enough, causing multiple daily overbookings. Each overbooking damages guest trust, incurs relocation costs, and risks OTA penalties.
1.3 Giải Pháp
A unified dashboard that:
- Connects OTA accounts and auto-discovers properties from each OTA extranet
- Polls OTA extranets for new bookings every 2-3 minutes
- Automatically pushes updated availability to all connected OTAs
- Alerts staff on sync failures or potential overbookings
- Provides a single view of all bookings and availability, filterable by country
1.4 Cách Tiếp Cận Kỹ Thuật
Reverse-engineer OTA extranet API calls via HTTP request interception (Playwright). Replay captured requests to read bookings and write availability updates. No official OTA API partnerships required.
1.5 Ràng Buộc
- Solo developer
- Zero infrastructure budget (self-hosted on existing server)
- OTA extranet automation carries ToS risk
- Mixed 2FA methods across OTA accounts
- One OTA account may manage multiple properties
- 3 countries with different currencies and timezones
2. Tác Nhân (Vai Trò Người Dùng)
| ID | Role | Preset Key | Description | Access Level |
|---|---|---|---|---|
| U-01 | Super Admin | super_admin | System owner. Full access to all modules and all roles. Cannot be deleted. | Full access (country=null) |
| U-02 | Admin | admin | System administrator. Full access except cannot manage other SA users. | Full access (country=null) |
| U-03 | Manager | manager | Operations manager overseeing properties. Full CRUD on operational modules, view-only on settings/users. | Full or country-scoped |
| U-04 | OTA Operator | ota | Staff managing OTA accounts, connections, sync, room mappings. | Country-scoped |
| U-05 | Customer Service | cs | Handles bookings and alerts. View/edit bookings, resolve alerts. | Country-scoped |
| U-06 | Finance | fin | Views booking revenue, rates, availability. Read-only. | Country-scoped |
| U-07 | Purchasing | po | Placeholder for future ERP purchasing module. Minimal access. | Country-scoped |
Note: Roles are stored in a roles table (E-14) with bitwise permissions per module. Admins can create custom roles beyond these 7 presets. See FR-20–FR-23.
3. Yêu Cầu Chức Năng (FR-xx)
Phase 1 — P1: Anti-Overbooking Core
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-01 | OTA Account Management | P1 | ⚠️ PARTIAL | CRUD + encrypted credentials ✅. Test connection & refresh session are stubs (return hardcoded messages). No Playwright integration. | U-01 |
| FR-02 | Property Discovery & Import | P1 | ❌ NOT IMPL | OTA adapters are all stubs — fetchProperties() returns empty. No property discovery, no cross-OTA matching. Manual property CRUD works. | U-01 |
| FR-03 | Booking Pull | P1 | ⚠️ PARTIAL | BullMQ orchestration ✅, booking upsert ✅, dedup ✅. But OTA adapters fetchBookings() returns empty. Also always fetches from new Date(0) (no incremental pull). Room mapping uses first available instead of matching by OTA room ID. | System |
| FR-04 | Availability Auto-Sync | P1 | ⚠️ PARTIAL | Availability calculation logic ✅, overbooking detection ✅, BullMQ processors ✅. But OTA adapters pushAvailability() is a stub — never actually pushes to any OTA. | System |
| FR-05 | Property & Room Management | P1 | ✅ COMPLETE | CRUD for properties, room types, OTA connections, room mappings all working. | U-01, U-02 |
| FR-06 | Sync Status Dashboard | P1 | ✅ COMPLETE | KPI cards, sync status panel, recent bookings — all fetch real DB data. | U-01, U-02 |
| FR-07 | Overbooking Alert | P1 | ⚠️ PARTIAL | Alert creation on overbooking ✅, alert list/resolve ✅, dashboard banner ✅. LINE Notify not implemented (stub returns "not available in P1"). Email notification service exists but depends on SMTP config. | U-01, U-02 |
| FR-08 | Country-Scoped Access | P1 | ✅ COMPLETE | Country scope guard, decorator, helper all working. Staff locked to country, manager sees all. | U-01, U-02 |
Phase 2 — P2: Operational Efficiency
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-09 | Bulk Rate Update | P2 | ❌ NOT IMPL | Rate and RateRule models exist in Prisma but no service, no controller, no API endpoints, no frontend UI. push_rates job type defined but no processor. | U-02 |
| FR-10 | Availability Calendar | P2 | ❌ NOT IMPL | No route, no component, no page. Not built at all. | U-01, U-02 |
| FR-11 | Booking Timeline | P2 | ❌ NOT IMPL | No route, no component, no page. Not built at all. | U-01, U-02 |
| FR-12 | Cancellation Sync | P2 | ❌ NOT IMPL | No cancelBooking method in OTA adapter interface. Booking status can be changed to "cancelled" via workflow engine, but no OTA-side detection or sync. | System |
Phase 2.5 — P2: User Profile & Preferences
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-16 | User Profile Management | P2 | ✅ COMPLETE | Profile page with name/email/locale edit, live format preview. | U-01, U-02 |
| FR-17 | Password Change | P2 | ✅ COMPLETE | Current password verification, 8-char min, mustChangePassword flow. | U-01, U-02 |
| FR-18 | Auth Hydration | P2 | ✅ COMPLETE | GET /users/me on mount, 401 → redirect to login. | System |
| FR-19 | Client-Side Theme | P2 | ✅ COMPLETE | Dark/light toggle, localStorage persistence. | U-01, U-02 |
Phase 2.7 — P2: Role & Permission Management
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-20 | Role CRUD | P2 | ✅ COMPLETE | CRUD with permission map, system role protection, 7 presets seeded. | U-01, U-02 |
| FR-21 | Bitwise Permission System | P2 | ✅ COMPLETE | JSONB bitmask per module, bitwise AND checks. | System |
| FR-22 | Permission-Based Access Control | P2 | ✅ COMPLETE | PermissionsGuard + @RequirePermission decorator. Frontend sidebar/button hiding. | System |
| FR-23 | Role Assignment | P2 | ✅ COMPLETE | Role selector in Users tab, JWT includes permissions. | U-01, U-02 |
Phase 2.9 — P2: Booking Status Workflow Engine
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-25 | Booking Status Transitions | P2 | ✅ COMPLETE | Transition table with FK integrity, UNIQUE(from,to), allowedRoles, hooks. | U-01, U-02 |
| FR-26 | Booking Status Change | P2 | ✅ COMPLETE | PATCH with transition validation, role check, country scope, hook execution. | U-01–U-05 |
| FR-27 | Transition Hooks | P2 | ✅ COMPLETE | audit_log, update_availability, send_notification hooks execute on transitions. | System |
| FR-28 | Per-Status UI Config | P2 | ✅ COMPLETE | JSONB uiConfig on BookingStatusDef with per-role sections/buttons/editableFields. | U-01, U-02 |
| FR-29 | Workflow Config Page | P2 | ✅ COMPLETE | Settings workflow tab with status list, transitions CRUD, Mermaid preview. | U-01, U-02 |
| FR-30 | Workflow Visualization | P2 | ✅ COMPLETE | Mermaid stateDiagram-v2 with role annotations, client-side rendering. | U-01, U-02 |
Phase 2.8 — P2: Client-Side Data Optimization
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-24 | Client-Side Data Caching | P2 | ✅ COMPLETE | SWR for all GETs, mutate on change, polling with tab-visibility pause, debounced filters. | System |
Phase 2.10 — P2: Supplier Room Allocation
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-31 | Supplier Room Allocation | P2 | ❌ NOT IMPL | M:N junction table linking Supplier↔RoomType with roomCount. Replace property-level supplier link. Soft warning when SUM(allocations) != totalRooms. | U-01, U-02, U-04 |
| FR-32 | Supplier Allocation CRUD | P2 | ❌ NOT IMPL | Create/update/delete allocations per room type. Soft delete (isActive). Both entry points: room-type context and supplier context. | U-01, U-02, U-04 |
| FR-33 | Supplier Allocation Display | P2 | ❌ NOT IMPL | Room type table shows supplier badges with room counts. Warning badge on mismatch. Supplier list shows total rooms allocated. Supplier detail shows allocations grouped by property. | U-01–U-07 |
| FR-34 | Booking Supplier Attribution | P2 | ❌ NOT IMPL | Optional nullable supplierId on Booking for future manual attribution. Column ready, no UI yet. | System |
Phase 3 — P3: Intelligence
| ID | Feature | Priority | Status | Description | Actor |
|---|---|---|---|---|---|
| FR-13 | Rate Parity Checker | P3 | ❌ NOT IMPL | Not built. | U-01 |
| FR-14 | Revenue Analytics | P3 | ❌ NOT IMPL | Not built. | U-01 |
| FR-15 | Rate Rules Engine | P3 | ❌ NOT IMPL | Not built. DB models exist but no logic. | U-01 |
4. Danh Sách Màn Hình (S-xx)
| ID | Screen | Phase | Status | Description | Related FR |
|---|---|---|---|---|---|
| S-01 | Login | P1 | ✅ | Email/password authentication for staff | — |
| S-02 | Dashboard | P1 | ✅ | Sync status overview with country tabs, alerts, recent bookings, KPI cards | FR-06, FR-07, FR-08 |
| S-03 | Properties List | P1 | ✅ | List all properties with sync health indicators, filterable by country | FR-05 |
| S-04 | Property Detail | P1 | ✅ | Property info, room types, connected OTAs (via OTA accounts) | FR-05 |
| S-05 | OTA Accounts List | P1 | ⚠️ | List works. But links to /ota-accounts/[id] → 404 (detail page not built). Test/refresh are stubs. | FR-01 |
| S-06 | Connect OTA Account | P1 | ✅ | Multi-step wizard (select OTA → credentials → submit). No real Playwright test. | FR-01 |
| S-07 | Import Properties | P1 | ❌ | Not built. OTA adapter fetchProperties() is a stub. No import UI. | FR-02 |
| S-08 | Bookings List | P1 | ✅ | Filters, search, pagination, resizable columns, CSV export | FR-03 |
| S-09 | Booking Detail | P1 | ✅ | Guest/stay/property info, status transitions, revert, audit history | FR-03, FR-26, FR-28 |
| S-10 | Availability Calendar | P2 | ❌ | Not built. No route, no component. | FR-10 |
| S-11 | Rate Manager | P2 | ❌ | Not built. No route, no component, no backend. | FR-09 |
| S-12 | Booking Timeline | P2 | ❌ | Not built. No route, no component. | FR-11 |
| S-13 | Rate Parity Report | P3 | ❌ | Not built. | FR-13 |
| S-14 | Analytics Dashboard | P3 | ❌ | Not built. | FR-14 |
| S-15 | Rate Rules Config | P3 | ❌ | Not built. | FR-15 |
| S-16 | Settings | P1 | ✅ | All 7 tabs working: Users, Roles, Booking Statuses, Workflow, Preferences, Notifications, System | FR-08 |
| S-17 | Sync Job Log | P1 | ⚠️ | List/filter/auto-refresh works. Missing "Force Sync" button (backend endpoint exists). | FR-04 |
| S-18 | User Profile | P2 | ✅ | Profile edit, password change, theme toggle | FR-16, FR-17, FR-19 |
| S-19 | Role Management | P2 | ✅ | Permission matrix editor for preset and custom roles. In Settings Roles tab. | FR-20, FR-21, FR-23 |
| S-20 | Workflow Config | P2 | ✅ | Transitions CRUD, hooks config, Mermaid preview. In Settings Workflow tab. | FR-25, FR-28, FR-29, FR-30 |
| S-21 | Suppliers List | P2 | ✅ | Paginated table: code, name, country, phone, email, total rooms allocated. Import CSV. Export CSV. | FR-31, FR-33 |
| S-22 | Supplier Detail | P2 | ✅ | Basic info + bank details cards. Room allocations table grouped by property (replaces linked properties). | FR-31, FR-33 |
| S-23 | Supplier Allocation Manager | P2 | ❌ | Modal on room type table row. Add/edit/remove supplier allocations per room type. Shows total vs allocated with warning. | FR-31, FR-32 |
5. Danh Sách Thực Thể (E-xx)
| ID | Entity | Description | Key Fields |
|---|---|---|---|
| E-01 | User | Internal staff account | id, email, password_hash, name, role_id(FK→Role), country(nullable), locale(vi/id/ms/en, default:en), created_at |
| E-02 | Property | Hotel/lodging managed by operator | id, name, country(VN/ID/MY), timezone, currency, address, is_active. |
| E-03 | RoomType | Room category within a property | id, property_id, name, base_rate, total_rooms, max_occupancy, is_active |
| E-04 | OtaAccount | Top-level OTA login account (one per OTA credential set) | id, ota(booking/agoda/traveloka/expedia), label, credentials_encrypted, two_factor_method(none/totp/manual), two_factor_secret, session_data(jsonb), status(active/expired/error/requires_2fa), last_session_refresh, user_id, country_code, created_at |
| E-05 | OtaConnection | Lightweight link between property and OTA account | id, property_id, ota_account_id, ota_property_id, is_active, created_at |
| E-06 | OtaRoomMapping | Maps internal room type to OTA IDs | id, room_type_id, ota_connection_id, ota_room_id, ota_rate_plan_id |
| E-07 | Availability | Daily availability per room type | id, room_type_id, date, total_rooms, booked_rooms, blocked_rooms |
| E-08 | Booking | Reservation from any OTA | id, property_id, room_type_id, supplier_id(nullable FK→Supplier, future attribution), ota_type, ota_booking_id(unique), guest_name, guest_email, check_in, check_out, num_rooms, num_guests, status(confirmed/cancelled/no_show), total_amount, currency, raw_data(jsonb), created_at |
| E-09 | SyncJob | Track sync operations | id, ota_connection_id, job_type(pull_bookings/push_availability/push_rates/verify), status(pending/running/completed/failed), error, payload(jsonb), started_at, completed_at |
| E-10 | Rate | Daily rate per room type per OTA | id, room_type_id, ota_connection_id, date, rate_amount(integer), currency |
| E-11 | Alert | Overbooking and sync failure alerts | id, property_id, alert_type(overbooking/sync_failure/session_expired), severity(critical/warning/info), message, is_resolved, resolved_by, created_at, resolved_at |
| E-12 | AuditLog | Track all system changes | id, entity_type, entity_id, action(create/update/delete), old_value(jsonb), new_value(jsonb), performed_by, created_at |
| E-13 | RateRule | Automated rate adjustment rules | id, property_id, room_type_id, ota_connection_id, rule_type(markup/discount/seasonal), value, start_date, end_date, is_active |
| E-14 | Role | Named role with bitwise permissions per module | id, name(unique key), label(display), description, permissions(JSONB: {module: bitmask}), is_system(bool), is_active, created_at |
| E-15 | BookingStatusTransition | State machine transition rule between two statuses | id(UUID), from_key(FK→E-08b), to_key(FK→E-08b), allowed_roles(JSONB[]), hooks(JSONB[]), sort_order, is_active, created_at. UNIQUE(from_key, to_key), CHECK(from_key≠to_key) |
| E-16 | BookingStatusDef.uiConfig | Per-role UI visibility config (JSONB column on E-08b) | { "roleName": { sections: string[], buttons: string[], editableFields: string[] } }. Wildcard "*" as default. Controls booking detail page rendering per status×role |
| E-17 | SupplierRoomAllocation | M:N junction linking Supplier to RoomType with room count | id, supplier_id(FK→Supplier), room_type_id(FK→RoomType), room_count(int), notes(nullable), is_active(default true), created_at, updated_at. UNIQUE(supplier_id, room_type_id) |
6. Yêu Cầu Phi Chức Năng
6.1 Performance
| Metric | Target |
|---|---|
| Booking detection latency | < 3 minutes (poll interval) |
| Availability push latency | < 60 seconds after detection |
| End-to-end sync | < 5 minutes |
| Dashboard load time | < 2 seconds |
| Concurrent OTA sessions | 400+ (100 properties x 4 OTAs, shared via OTA accounts) |
6.2 Security
| Requirement | Implementation |
|---|---|
| OTA credential storage | AES-256-GCM encryption at rest |
| Internal auth | bcrypt password hashing, JWT sessions |
| Session management | HttpOnly cookies, CSRF protection |
| Data protection | PDPA/PDPD/PP71 compliant (VN/ID/MY) |
| Access control | Bitwise permission-based (JSONB per module: V/C/E/D bitmask) + country scoping. 7 preset roles + custom roles |
| Audit trail | All mutations logged in E-12 |
6.3 Reliability
| Requirement | Implementation |
|---|---|
| Job retry | BullMQ exponential backoff, max 3 retries |
| Session recovery | Auto-detect expired sessions, alert for re-login |
| Data consistency | PostgreSQL transactions for availability updates |
| Monitoring | Sync failure alerts within 1 minute |
| Backup | Daily PostgreSQL backup |
6.4 Scalability
| Dimension | Target |
|---|---|
| Properties | 100+ |
| Countries | 3 (VN, ID, MY) |
| OTA channels | 4 |
| OTA accounts | ~12-20 (few accounts per OTA per country) |
| Total connections | ~400+ |
| Bookings/day | ~200-800 |
| Sync jobs/hour | ~4,800+ (staggered scheduling) |
6.5 Tech Stack
| Component | Technology |
|---|---|
| Backend | NestJS (TypeScript) |
| Frontend | Next.js 14+ (App Router, TypeScript) |
| Database | PostgreSQL 16 |
| Cache/Queue | Redis 7 + BullMQ |
| ORM | Prisma |
| Browser Automation | Playwright |
| Monorepo | Turborepo |
| Auth | Passport.js + JWT (httpOnly cookie) |
| Notifications | nodemailer (LINE Notify stub only) |
7. Quyết Định Quan Trọng (D-xx)
| ID | Decision | Context | Chosen | Rationale |
|---|---|---|---|---|
| D-01 | OTA integration method | No official API access | HTTP request replay + Playwright fallback | Fastest path; replay intercepted extranet requests |
| D-02 | Backend language | Rust vs TypeScript | TypeScript (NestJS) | Solo dev; Playwright is JS-native; shared types with frontend |
| D-03 | Sync strategy | Push-only vs push+verify | Push with polling verification | Push alone unreliable; periodic verification catches silent failures |
| D-04 | Anti-overbooking | Optimistic vs pessimistic | Pessimistic locking + buffer rooms | Overbooking cost is high; prefer false negatives over overbookings |
| D-05 | Currency handling | Decimal vs integer | Integer (cents/smallest unit) | VND and IDR have no decimals; consistent integer math avoids float errors |
| D-06 | Timezone | Local vs UTC storage | UTC storage, local display | Indonesia has 3 timezones; UTC avoids conversion bugs |
| D-07 | PMS scope | Build vs buy | Defer (out of scope) | PMS is separate product; focus on channel management first |
| D-08 | OTA credential model | Per-property vs per-account | Per-account (OtaAccount entity) | One OTA login manages N properties; avoids duplicate credentials and sessions |
| D-09 | Onboarding flow | Property-first vs OTA-first | OTA-first | Matches real workflow: OTA accounts exist before properties are added to system |
| D-10 | Country scoping | Global access vs country-scoped | Country-scoped staff + global manager | 100+ properties across 3 countries; staff only needs their country |
| D-11 | Supported countries | TH/VN/ID vs VN/ID/MY | VN/ID/MY only | Thailand removed per business decision; Malaysia added |
| D-12 | OTA-country mapping | DB table vs config constant | Hardcoded config constant | YAGNI — 4 OTAs x 3 countries = 12 combos. Static config sufficient |
| D-13 | Permission model | Enum roles vs bitwise permissions | Bitwise JSONB permissions | System expanding to ERP; enum can't express per-module CRUD. JSONB {module: bitmask} is extensible, uses bitwise AND for checks, and supports unlimited modules without schema changes |
| D-14 | Permission storage | Single BigInt vs JSONB map | JSONB map per module | BigInt limited to 16 modules (64 bits / 4 bits). JSONB supports unlimited modules, is human-readable in queries, and PostgreSQL indexes JSONB efficiently |
| D-15 | Role presets | Hardcoded vs DB-stored | DB-stored with is_system flag | Presets seeded on deploy, protected from deletion. Custom roles addable by admin. Avoids code changes for new roles |
| D-16 | Client-side caching library | React Query vs SWR vs manual | SWR | 4KB bundle, hooks-only API, built by Vercel for Next.js. Stale-while-revalidate pattern fits this app's simple GET/mutate workflows. React Query is overkill for current needs |
| D-17 | Workflow data model | All-JSONB vs fully-normalized vs hybrid | Hybrid (transition table + JSONB uiConfig) | Transitions need FK integrity and queryability (relational). UI config is always read as blob (JSONB). Best balance of normalization and pragmatism |
| D-18 | Hooks location | Hooks on statuses vs transitions | Hooks on transitions | Same status pair can have different hooks depending on direction. E.g., confirmed→cancelled triggers availability restore, but cancelled→confirmed does not. Transition-level hooks are more precise |
| D-19 | Supplier model level | Property-level vs room-type-level | Room-type-level (M:N junction) | One supplier provides rooms across multiple properties. Same room type can be split across suppliers by count. Accounting-only — zero OTA sync impact |
| D-20 | Supplier allocation validation | Hard block vs soft warning | Soft warning | SUM(roomCount) != totalRooms shows warning in API response and UI badge. Not blocked — allows flexible allocation during data entry |
| D-21 | Supplier allocation delete | Hard delete vs soft delete | Soft delete (isActive) | Consistent with other entities. Inactive allocations excluded from SUM checks and display |
8. Ma Trận OTA-Quốc Gia
| OTA | VN | ID | MY |
|---|---|---|---|
| Booking.com | Yes | Yes | Yes |
| Agoda | Yes | Yes | Yes |
| Traveloka | Yes | Yes | Yes |
| Expedia | Yes | Yes | Yes |
All 4 OTAs available in all 3 countries. Matrix exists as guardrail for future additions.
9. Ngoài Phạm Vi
- PMS features (guest management, check-in/out, housekeeping, invoicing)
- Direct booking engine
- Mobile native app (responsive web sufficient)
- Multi-language UI (English-only, internal tool)
- Payment processing (handled by OTAs)
- Guest communication (handled by OTAs)
- SaaS/multi-tenant architecture
- Official OTA API partnerships (future migration path)
- Thailand market (removed)
10. Giả Định
| # | Assumption | Risk if Wrong |
|---|---|---|
| 1 | OTA extranet API calls can be captured and replayed | Entire approach fails |
| 2 | OTA sessions persist for hours/days | Frequent re-auth breaks automation |
| 3 | 3-5 min sync latency acceptable | Overbookings still occur within window |
| 4 | OTAs won't detect/block automation at low volume | Account ban risk |
| 5 | Staff will adopt the dashboard | Tool goes unused |
| 6 | Room type mappings between OTAs are stable | Mapping breaks cause wrong updates |
| 7 | One OTA account manages multiple properties | Schema design based on this |
11. Rủi Ro
| Risk | Severity | Likelihood | Mitigation |
|---|---|---|---|
| OTA detects automation, bans account | CRITICAL | Medium | Rate limiting, human-like request patterns, start with 2-3 test properties |
| OTA extranet API changes | HIGH | High | Isolated adapters per OTA, health monitoring, fast-fix pipeline |
| 2FA blocks automation | HIGH | High | TOTP: automate via secret. SMS/Email: manual login + long session persistence |
| Solo dev maintenance burden | HIGH | High | Start with 2 OTAs in Phase 1, stabilize before expanding |
| Overbooking within sync window | MEDIUM | Medium | Buffer rooms, pessimistic locking, instant alerts |
| Cross-OTA property matching error | MEDIUM | Low | Always show confirmation UI, allow undo, manual override |
| 400+ connections overwhelm server | MEDIUM | Medium | Country-scoped filtering reduces active load; staggered scheduling |