# TrisolarisPMS API Usage ## API Docs Path - `/home/androidlover5842/IdeaProjects/TrisolarisServer/docs` ## 1) Booking ### Create booking POST /properties/{propertyId}/bookings Auth: ADMIN/MANAGER/STAFF Body (JSON) Required: - expectedCheckInAt (String, ISO-8601, required) - expectedCheckOutAt (String, ISO-8601, required) Optional: - source (String, default "WALKIN") - transportMode (String enum) - adultCount (Int) - totalGuestCount (Int) - notes (String) { "source": "WALKIN", "expectedCheckInAt": "2026-01-28T12:00:00+05:30", "expectedCheckOutAt": "2026-01-29T10:00:00+05:30", "transportMode": "CAR", "adultCount": 2, "totalGuestCount": 3, "notes": "Late arrival" } Behavior If expectedCheckInAt >= now(property timezone) -> booking becomes CHECKED_IN, and checkinAt is set, expected fields are null. Response { "id": "uuid", "status": "OPEN|CHECKED_IN", "checkInAt": "2026-01-28T12:00:00+05:30" | null, "expectedCheckInAt": "..." | null, "expectedCheckOutAt": "..." | null } --- ### List bookings GET /properties/{propertyId}/bookings Optional query param: - status (comma-separated), e.g. status=OPEN,CHECKED_IN Behavior: - If status is omitted, returns all bookings for the property (newest first). Response: List of BookingListItem with id, status, guestId, guestName, guestPhone, roomNumbers, source, times, counts, expectedGuestCount, notes. Notes: - It returns active room stays (toAt = null) for each booking. --- ### Booking details GET /properties/{propertyId}/bookings/{bookingId} Includes: - Guest info (name/phone/nationality/address/signatureUrl) - Room numbers (active stays if present; otherwise all stays) - Travel fields (fromCity/toCity/memberRelation) - Transport mode, expected/actual times - Counts (male/female/child/total/expected) - Registered by (createdBy name/phone) - totalNightlyRate (sum of nightlyRate across shown rooms) - balance: expectedPay, amountCollected, pending --- ### Check-in (creates RoomStay) POST /properties/{propertyId}/bookings/{bookingId}/check-in Auth: ADMIN/MANAGER Body Required: - roomIds (List) Optional: - checkInAt (String) - transportMode (String enum) - nightlyRate (Long) - rateSource (MANUAL|RATE_PLAN|OTA) - ratePlanCode (String) - currency (String) - notes (String) { "roomIds": ["uuid1","uuid2"], "checkInAt": "2026-01-28T12:00:00+05:30", "nightlyRate": 2500, "rateSource": "MANUAL", "ratePlanCode": "EP", "currency": "INR", "notes": "Late arrival" } --- ### Pre-assign room stay POST /properties/{propertyId}/bookings/{bookingId}/room-stays Auth: ADMIN/MANAGER Body Required: - roomId (UUID) - fromAt (String) - toAt (String) Optional: - nightlyRate (Long) - rateSource (MANUAL|RATE_PLAN|OTA) - ratePlanCode (String) - currency (String) - notes (String) { "roomId": "uuid", "fromAt": "2026-01-29T12:00:00+05:30", "toAt": "2026-01-30T10:00:00+05:30", "nightlyRate": 2800, "rateSource": "RATE_PLAN", "ratePlanCode": "EP", "currency": "INR" } --- ### Active room stays GET /properties/{propertyId}/room-stays/active Auth: any member except AGENT-only Response: list of ActiveRoomStayResponse [ { "roomStayId":"uuid", "bookingId":"uuid", "guestId":"uuid-or-null", "guestName":"Name", "guestPhone":"+9111...", "roomId":"uuid", "roomNumber":"101", "roomTypeName":"DELUXE", "fromAt":"2026-01-29T12:00:00+05:30", "checkinAt":"2026-01-29T12:05:00+05:30", "expectedCheckoutAt":"2026-01-30T10:00:00+05:30" } ] --- ### Change room (move guest) POST /properties/{propertyId}/room-stays/{roomStayId}/change-room Auth: ADMIN/MANAGER/STAFF Body { "newRoomId":"uuid", "movedAt":"2026-01-30T15:00:00+05:30", "idempotencyKey":"any-unique-string" } Response { "oldRoomStayId":"uuid", "newRoomStayId":"uuid", "oldRoomId":"uuid", "newRoomId":"uuid", "movedAt":"2026-01-30T15:00:00+05:30" } --- ### Update expected dates POST /properties/{propertyId}/bookings/{bookingId}/expected-dates Rules: - OPEN → can update expectedCheckInAt and/or expectedCheckOutAt - CHECKED_IN → can update only expectedCheckOutAt - CHECKED_OUT / CANCELLED / NO_SHOW → forbidden Body { "expectedCheckInAt": "2026-01-29T12:00:00+05:30", "expectedCheckOutAt": "2026-01-30T10:00:00+05:30" } --- ## 2) Guests ### Create guest + link to booking POST /properties/{propertyId}/guests Auth: property member Body (required): - bookingId (UUID) Optional: - phoneE164 (String) - name (String) - nationality (String) - addressText (String) { "bookingId": "uuid", "phoneE164": "+911111111111", "name": "John", "nationality": "IN", "addressText": "Varanasi" } Behavior: - If phone already exists -> links existing guest to booking and returns it. - If booking already has a guest -> 409. Response (GuestResponse) { "id": "uuid", "name": "John", "phoneE164": "+911111111111", "nationality": "IN", "addressText": "Varanasi", "signatureUrl": "/properties/{propertyId}/guests/{guestId}/signature/file", "vehicleNumbers": [], "averageScore": null } --- ### Add guest vehicle + link to booking POST /properties/{propertyId}/guests/{guestId}/vehicles Auth: property member Body: { "vehicleNumber": "UP32AB1234", "bookingId": "uuid" } --- ### Upload signature (SVG only) POST /properties/{propertyId}/guests/{guestId}/signature Auth: ADMIN/MANAGER Multipart: - file (SVG) --- ### Download signature GET /properties/{propertyId}/guests/{guestId}/signature/file Auth: property member Returns image/svg+xml. --- ### Search guest by phone GET /properties/{propertyId}/guests/search?phone=+911111111111 --- ## 3) Room Types (default rate + rate resolve) ### Room type create/update Fields now include defaultRate: RoomTypeUpsertRequest { "code": "DELUX", "name": "Deluxe", "baseOccupancy": 2, "maxOccupancy": 3, "sqFeet": 150, "bathroomSqFeet": 30, "defaultRate": 2500, "active": true, "otaAliases": [], "amenityIds": [] } ### Resolve preset rate for date GET /properties/{propertyId}/room-types/{roomTypeCode}/rate?date=YYYY-MM-DD&ratePlanCode=optional Auth: public if no auth, or member Response { "roomTypeCode": "DELUX", "rateDate": "2026-02-01", "rate": 2800, "currency": "INR", "ratePlanCode": "WEEKEND" } --- ## 4) Rate Plans + Calendar ### Create rate plan POST /properties/{propertyId}/rate-plans Auth: ADMIN/MANAGER Body Required: - code (String) - name (String) - roomTypeCode (String) - baseRate (Long) Optional: - currency (String, default property currency) { "code":"WEEKEND", "name":"Weekend", "roomTypeCode":"DELUX", "baseRate":2800, "currency":"INR" } Response RatePlanResponse ### List plans GET /properties/{propertyId}/rate-plans?roomTypeCode=optional Auth: member ### Update PUT /properties/{propertyId}/rate-plans/{ratePlanId} Body: { "name":"Weekend", "baseRate":3000, "currency":"INR" } ### Delete DELETE /properties/{propertyId}/rate-plans/{ratePlanId} ### Calendar upsert (batch) POST /properties/{propertyId}/rate-plans/{ratePlanId}/calendar Body: Array [ { "rateDate":"2026-02-01", "rate":3200 }, { "rateDate":"2026-02-02", "rate":3500 } ] ### Calendar list GET /properties/{propertyId}/rate-plans/{ratePlanId}/calendar?from=YYYY-MM-DD&to=YYYY-MM-DD ### Calendar delete DELETE /properties/{propertyId}/rate-plans/{ratePlanId}/calendar/{rateDate} --- ## 5) RoomStay rate change (mid-stay renegotiation) POST /properties/{propertyId}/room-stays/{roomStayId}/change-rate Auth: ADMIN/MANAGER Body Required: - effectiveAt (String, ISO-8601) - nightlyRate (Long) - rateSource (MANUAL|RATE_PLAN|OTA) Optional: - ratePlanCode (String) - currency (String) { "effectiveAt": "2026-01-30T12:00:00+05:30", "nightlyRate": 2000, "rateSource": "MANUAL", "currency": "INR" } Response { "oldRoomStayId":"uuid", "newRoomStayId":"uuid", "effectiveAt":"..." } --- ### Check-out (closes all active stays on booking) POST /properties/{propertyId}/bookings/{bookingId}/check-out Auth: ADMIN/MANAGER Body { "checkOutAt":"2026-01-30T10:00:00+05:30", "notes":"optional" } Response: 204 No Content --- ### Bulk check-in (creates multiple room stays) POST /properties/{propertyId}/bookings/{bookingId}/check-in/bulk Body: { "stays": [ { "roomId": "uuid", "checkInAt": "2026-01-29T12:00:00+05:30", "checkOutAt": "2026-01-30T10:00:00+05:30", "nightlyRate": 6000, "rateSource": "MANUAL", "ratePlanCode": "EP", "currency": "INR" }, { "roomId": "uuid", "checkInAt": "2026-01-29T12:00:00+05:30", "checkOutAt": "2026-01-30T10:00:00+05:30", "nightlyRate": 8000, "rateSource": "MANUAL", "ratePlanCode": "EP", "currency": "INR" } ] } Behavior - Creates one RoomStay per stay with its own rate. - Sets booking CHECKED_IN, checkinAt = earliest stay check-in. - If any checkOutAt provided, booking expectedCheckoutAt = latest of those. - Rejects duplicate room IDs. - Rejects invalid stay date range (checkOutAt <= checkInAt). - Blocks occupied rooms. ## 6) Payments + Balance ### Add payment POST /properties/{propertyId}/bookings/{bookingId}/payments Auth: ADMIN/MANAGER/STAFF Body Required: - amount (Long) - method (CASH|CARD|UPI|BANK|ONLINE) Optional: - currency (String, default property currency) - reference (String) - notes (String) - receivedAt (String) { "amount": 1200, "method": "CASH", "currency": "INR", "reference": "RCP-123", "notes": "Advance" } Response { "id":"uuid", "bookingId":"uuid", "amount":1200, "currency":"INR", "method":"CASH", "reference":"RCP-123", "notes":"Advance", "receivedAt":"2026-01-28T12:00:00+05:30", "receivedByUserId":"uuid" } ### List payments GET /properties/{propertyId}/bookings/{bookingId}/payments ### Booking balance GET /properties/{propertyId}/bookings/{bookingId}/balance { "expectedPay": 2745, "amountCollected": 1200, "pending": 1545 } --- ## 7) Compose Notes - Use `androidx.compose.foundation.text.KeyboardOptions` for keyboard options imports. --- ## 8) Engineering Structure & Anti-Boilerplate Rules ### Non-negotiable coding rules - Duplicate code is forbidden. - Never add duplicate business logic in multiple files. - Never copy-paste permission checks, navigation decisions, mapping logic, or API call patterns. - If similar logic appears 2+ times, extract shared function/class immediately. - Prefer typed models/enums over raw strings for roles/status/flags. - Keep files small and purpose-driven; split before a file becomes hard to scan. ### Required project structure (current baseline) - `core/` -> cross-cutting business primitives/policies (e.g., auth policy, role enum). - `core/viewmodel/` -> shared ViewModel execution helpers (loading/error wrappers, common request runners). - `data/api/core/` -> API client, constants, token providers, aggregated API service. - `data/api/service/` -> Retrofit endpoint interfaces only. - `data/api/model/` -> DTO/request/response models. - `ui/navigation/` -> route model, navigation orchestrators, back-navigation rules. - `ui//` -> screen + state + viewmodel for that feature. ### How to implement future logic (mandatory workflow) 1. Define/extend domain type first (enum/data model/policy) instead of raw literals. 2. Add/extend API contract in `data/api/service` and models in `data/api/model`. 3. Add shared logic once (policy/helper/mapper) in `core` or feature-common layer. 4. Keep ViewModel thin: orchestrate calls, state, and errors only. 5. Keep UI dumb: consume state and callbacks; avoid business rules in composables. 6. If navigation changes, update `ui/navigation` only (single source of truth). 7. Before finishing, remove any newly introduced duplication and compile-check. 8. If 2+ ViewModels repeat loading/error coroutine flow, extract/use shared helper in `core/viewmodel`. 9. If Add/Edit screens differ only by initialization + submit callback, extract a feature-local shared form screen. 10. Prefer dedupe/organization improvements even if net LOC does not decrease, as long as behavior remains unchanged. ### PR/refactor acceptance checklist - No repeated role/permission checks across screens. - No repeated model mapping blocks (extract mapper/helper). - No giant god-file when it can be split by domain responsibility. - Imports/packages follow the structure above. - Build passes: `./gradlew :app:compileDebugKotlin`. ### Room DB synchronization rule (mandatory) - For any editable API-backed field, follow this write path only: `UI action -> server request -> Room DB update -> UI reacts from Room observers`. - Server is source of truth; do not bypass server by writing final business state directly from UI. - UI must render from Room-backed state, not from one-off API responses or direct text mutation. - Avoid forced full refresh after each mutation unless strictly required by backend limitations; prefer targeted Room updates for linked entities. - On mutation failure, keep prior DB state unchanged and surface error state to UI. ### Guest Documents Authorization (mandatory) - View access: `ADMIN`, `MANAGER` (and super admin). - Modify access (upload/delete): allowed only when booking status is `OPEN` or `CHECKED_IN`. - For `CHECKED_OUT`, `CANCELLED`, `NO_SHOW`: documents are read-only. - Never couple guest document permissions with Razorpay/settings permissions. ### Permission design guardrail - Do not reuse one feature's permission gate for another unrelated feature. - Add explicit policy methods in `core/auth/AuthzPolicy` for each feature capability. ### Refactor safety rule - Any package/file movement must include import updates in same change. - After refactor, compile check is mandatory: `./gradlew :app:compileDebugKotlin`.