Files
TrisolarisPMS/AGENTS.md
2026-02-04 13:41:06 +05:30

13 KiB

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

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 }


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

  • 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/<feature>/ -> 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.

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.