diff --git a/AGENTS.md b/AGENTS.md index c0107fb..3a43cf2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -205,6 +205,7 @@ Notes / constraints - Checkout validation: nightly rate must be within +/-20% of room type default rate (when default rate exists), and minimum stay duration must be at least 1 hour. - Room-type reservations: use booking room requests (`booking_room_request`) for quantity holds without room numbers; availability checks include active requests + occupied stays. - Cancellation policy engine (advance bookings): policy per property with `freeDaysBeforeCheckin` + `penaltyMode` (`NO_CHARGE`, `ONE_NIGHT`, `FULL_STAY`). On cancel/no-show, penalty charge ledger rows are auto-created (`CANCELLATION_PENALTY` / `NO_SHOW_PENALTY`) when within penalty window. +- API documentation source of truth: `docs/API_CATALOG.md`. Any API addition, removal, path change, method change, or behavior-impacting request/response change must update this doc in the same commit. Operational notes - Payment provider migrated: PayU removed; Razorpay now used for settings, QR, payment links, and webhooks. @@ -220,4 +221,5 @@ Access / ops notes (prod) - DB (dev): PostgreSQL `trisolaris` on `192.168.1.53:5432` (see `application-dev.properties` in the repo). - DB access (server): `sudo -u postgres psql -d trisolaris`. - Workflow: Always run build, commit, and push for changes unless explicitly told otherwise. +- Workflow rule for API changes: after code changes, update `docs/API_CATALOG.md`, then build, commit, and push. - Android workflow note: user always runs Shift+F10 in Android Studio to deploy updates. diff --git a/docs/API_CATALOG.md b/docs/API_CATALOG.md new file mode 100644 index 0000000..fc59962 --- /dev/null +++ b/docs/API_CATALOG.md @@ -0,0 +1,196 @@ +# API Catalog + +Source of truth for implemented HTTP endpoints. Generated from controller annotations in `src/main/kotlin/com/android/trisolarisserver/controller`. + +- Total endpoints: **125** +- Columns: method, path, handler (controller function). + +## System + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/` | `src/main/kotlin/com/android/trisolarisserver/controller/system/Health.kt:13` (`root`) | +| `GET` | `/health` | `src/main/kotlin/com/android/trisolarisserver/controller/system/Health.kt:8` (`health`) | + +## Auth + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/auth/me` | `src/main/kotlin/com/android/trisolarisserver/controller/auth/Auth.kt:43` (`me`) | +| `PUT` | `/auth/me` | `src/main/kotlin/com/android/trisolarisserver/controller/auth/Auth.kt:53` (`updateMe`) | +| `POST` | `/auth/verify` | `src/main/kotlin/com/android/trisolarisserver/controller/auth/Auth.kt:32` (`verify`) | + +## Properties & Users + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:91` (`listProperties`) | +| `POST` | `/properties` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:51` (`createProperty`) | +| `POST` | `/properties/access-codes/join` | `src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt:89` (`joinWithAccessCode`) | +| `PUT` | `/properties/{propertyId}` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:305` (`updateProperty`) | +| `POST` | `/properties/{propertyId}/access-codes` | `src/main/kotlin/com/android/trisolarisserver/controller/property/PropertyAccessCodes.kt:43` (`createAccessCode`) | +| `GET` | `/properties/{propertyId}/billing-policy` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:117` (`getBillingPolicy`) | +| `PUT` | `/properties/{propertyId}/billing-policy` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:134` (`updateBillingPolicy`) | +| `GET` | `/properties/{propertyId}/bookings` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:186` (`listBookings`) | +| `POST` | `/properties/{propertyId}/bookings` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:96` (`createBooking`) | +| `GET` | `/properties/{propertyId}/code` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:104` (`getPropertyCode`) | +| `GET` | `/properties/{propertyId}/razorpay-settings` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpaySettingsController.kt:32` (`getSettings`) | +| `PUT` | `/properties/{propertyId}/razorpay-settings` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpaySettingsController.kt:56` (`upsertSettings`) | +| `POST` | `/properties/{propertyId}/razorpay/return/failure` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayReturnController.kt:20` (`failure`) | +| `POST` | `/properties/{propertyId}/razorpay/return/success` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayReturnController.kt:14` (`success`) | +| `POST` | `/properties/{propertyId}/razorpay/webhook` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayWebhookCapture.kt:50` (`capture`) | +| `GET` | `/properties/{propertyId}/users` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:164` (`listPropertyUsers`) | +| `GET` | `/properties/{propertyId}/users/search` | `src/main/kotlin/com/android/trisolarisserver/controller/property/UserDirectory.kt:49` (`searchPropertyUsers`) | +| `DELETE` | `/properties/{propertyId}/users/{userId}` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:288` (`deletePropertyUser`) | +| `PUT` | `/properties/{propertyId}/users/{userId}/disabled` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:239` (`updatePropertyUserDisabled`) | +| `PUT` | `/properties/{propertyId}/users/{userId}/roles` | `src/main/kotlin/com/android/trisolarisserver/controller/property/Properties.kt:188` (`upsertPropertyUserRoles`) | +| `GET` | `/users` | `src/main/kotlin/com/android/trisolarisserver/controller/property/UserDirectory.kt:26` (`listAppUsers`) | + +## Bookings + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/bookings/{bookingId}` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:280` (`getBooking`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/balance` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingBalances.kt:31` (`getBalance`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/billing-policy` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:473` (`updateBillingPolicy`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/cancel` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:692` (`cancel`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/charges` | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Charges.kt:79` (`list`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/charges` | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Charges.kt:41` (`create`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/check-in/bulk` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:350` (`bulkCheckIn`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/check-out` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:524` (`checkOut`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/expected-dates` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:427` (`updateExpectedDates`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/link-guest` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:323` (`linkGuest`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/no-show` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:717` (`noShow`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments` | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Payments.kt:85` (`list`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments` | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Payments.kt:45` (`create`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/close` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayPaymentRequestsController.kt:92` (`closeRequest`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/payment-link` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayPaymentLinksController.kt:45` (`createPaymentLink`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:291` (`listQr`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:54` (`createQr`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/active` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:156` (`getActiveQr`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/close` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:178` (`closeActiveQr`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/close` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:210` (`closeQrById`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/events` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:243` (`qrEvents`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/qr/{qrId}/events/stream` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayQrPayments.kt:272` (`streamQrEvents`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/refund` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayRefundsController.kt:41` (`refund`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/payments/razorpay/requests` | `src/main/kotlin/com/android/trisolarisserver/controller/razorpay/RazorpayPaymentRequestsController.kt:44` (`listRequests`) | +| `DELETE` | `/properties/{propertyId}/bookings/{bookingId}/payments/{paymentId}` | `src/main/kotlin/com/android/trisolarisserver/controller/payment/Payments.kt:101` (`delete`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/room-requests` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingRoomRequests.kt:102` (`list`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/room-requests` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingRoomRequests.kt:45` (`create`) | +| `DELETE` | `/properties/{propertyId}/bookings/{bookingId}/room-requests/{requestId}` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingRoomRequests.kt:118` (`cancel`) | +| `POST` | `/properties/{propertyId}/bookings/{bookingId}/room-stays/{roomStayId}/check-out` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:580` (`checkOutRoomStay`) | +| `GET` | `/properties/{propertyId}/bookings/{bookingId}/stream` | `src/main/kotlin/com/android/trisolarisserver/controller/booking/BookingFlow.kt:301` (`streamBooking`) | + +## Guests & Documents + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/guests/search` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:74` (`search`) | +| `GET` | `/properties/{propertyId}/guests/visit-count` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:112` (`getVisitCount`) | +| `GET` | `/properties/{propertyId}/guests/{guestId}` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:101` (`getGuest`) | +| `PUT` | `/properties/{propertyId}/guests/{guestId}` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:44` (`updateGuest`) | +| `GET` | `/properties/{propertyId}/guests/{guestId}/documents` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:124` (`listDocuments`) | +| `POST` | `/properties/{propertyId}/guests/{guestId}/documents` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:63` (`uploadDocument`) | +| `GET` | `/properties/{propertyId}/guests/{guestId}/documents/stream` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:137` (`streamDocuments`) | +| `DELETE` | `/properties/{propertyId}/guests/{guestId}/documents/{documentId}` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:181` (`deleteDocument`) | +| `GET` | `/properties/{propertyId}/guests/{guestId}/documents/{documentId}/file` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestDocuments.kt:151` (`downloadDocument`) | +| `GET` | `/properties/{propertyId}/guests/{guestId}/ratings` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestRatings.kt:78` (`list`) | +| `POST` | `/properties/{propertyId}/guests/{guestId}/ratings` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/GuestRatings.kt:40` (`create`) | +| `POST` | `/properties/{propertyId}/guests/{guestId}/signature` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:168` (`uploadSignature`) | +| `GET` | `/properties/{propertyId}/guests/{guestId}/signature/file` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:193` (`downloadSignature`) | +| `POST` | `/properties/{propertyId}/guests/{guestId}/vehicles` | `src/main/kotlin/com/android/trisolarisserver/controller/guest/Guests.kt:128` (`addVehicle`) | + +## Rooms & Images + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/rooms` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:65` (`listRooms`) | +| `POST` | `/properties/{propertyId}/rooms` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:276` (`createRoom`) | +| `GET` | `/properties/{propertyId}/rooms/availability` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:125` (`roomAvailability`) | +| `GET` | `/properties/{propertyId}/rooms/availability-range` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:183` (`roomAvailabilityRange`) | +| `GET` | `/properties/{propertyId}/rooms/available` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:146` (`availableRooms`) | +| `GET` | `/properties/{propertyId}/rooms/available-range-with-rate` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:220` (`availableRoomsWithRate`) | +| `GET` | `/properties/{propertyId}/rooms/board` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:85` (`roomBoard`) | +| `GET` | `/properties/{propertyId}/rooms/board/stream` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:113` (`roomBoardStream`) | +| `GET` | `/properties/{propertyId}/rooms/by-type/{roomTypeCode}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:159` (`roomsByType`) | +| `DELETE` | `/properties/{propertyId}/rooms/{roomId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:365` (`deleteRoom`) | +| `PUT` | `/properties/{propertyId}/rooms/{roomId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/Rooms.kt:322` (`updateRoom`) | +| `POST` | `/properties/{propertyId}/rooms/{roomId}/cards/prepare-temp` | `src/main/kotlin/com/android/trisolarisserver/controller/card/TemporaryRoomCards.kt:43` (`prepareTemporary`) | +| `POST` | `/properties/{propertyId}/rooms/{roomId}/cards/temp` | `src/main/kotlin/com/android/trisolarisserver/controller/card/TemporaryRoomCards.kt:71` (`issueTemporary`) | +| `GET` | `/properties/{propertyId}/rooms/{roomId}/images` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:54` (`list`) | +| `POST` | `/properties/{propertyId}/rooms/{roomId}/images` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:83` (`upload`) | +| `PUT` | `/properties/{propertyId}/rooms/{roomId}/images/reorder-room` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:196` (`reorderRoomImages`) | +| `PUT` | `/properties/{propertyId}/rooms/{roomId}/images/reorder-room-type` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:224` (`reorderRoomTypeImages`) | +| `DELETE` | `/properties/{propertyId}/rooms/{roomId}/images/{imageId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:126` (`delete`) | +| `GET` | `/properties/{propertyId}/rooms/{roomId}/images/{imageId}/file` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:256` (`file`) | +| `PUT` | `/properties/{propertyId}/rooms/{roomId}/images/{imageId}/tags` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImages.kt:177` (`updateTags`) | + +## Room Types + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/room-types` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:48` (`listRoomTypes`) | +| `POST` | `/properties/{propertyId}/room-types` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:105` (`createRoomType`) | +| `GET` | `/properties/{propertyId}/room-types/{roomTypeCode}/images` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypeImages.kt:28` (`listByRoomType`) | +| `GET` | `/properties/{propertyId}/room-types/{roomTypeCode}/rate` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:59` (`resolveRate`) | +| `DELETE` | `/properties/{propertyId}/room-types/{roomTypeId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:187` (`deleteRoomType`) | +| `PUT` | `/properties/{propertyId}/room-types/{roomTypeId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomTypes.kt:152` (`updateRoomType`) | + +## Room Stays & Cards + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/room-stays/active` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomStays.kt:41` (`listActiveRoomStays`) | +| `GET` | `/properties/{propertyId}/room-stays/cards/{cardIndex}` | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:157` (`getCardByIndex`) | +| `POST` | `/properties/{propertyId}/room-stays/cards/{cardIndex}/revoke` | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:136` (`revoke`) | +| `GET` | `/properties/{propertyId}/room-stays/{roomStayId}/cards` | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:124` (`list`) | +| `POST` | `/properties/{propertyId}/room-stays/{roomStayId}/cards` | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:80` (`issue`) | +| `POST` | `/properties/{propertyId}/room-stays/{roomStayId}/cards/prepare` | `src/main/kotlin/com/android/trisolarisserver/controller/card/IssuedCards.kt:47` (`prepare`) | +| `POST` | `/properties/{propertyId}/room-stays/{roomStayId}/void` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomStays.kt:77` (`voidRoomStay`) | + +## Rate Plans + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/rate-plans` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:78` (`list`) | +| `POST` | `/properties/{propertyId}/rate-plans` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:49` (`create`) | +| `DELETE` | `/properties/{propertyId}/rate-plans/{ratePlanId}` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:111` (`delete`) | +| `PUT` | `/properties/{propertyId}/rate-plans/{ratePlanId}` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:94` (`update`) | +| `GET` | `/properties/{propertyId}/rate-plans/{ratePlanId}/calendar` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:169` (`listCalendar`) | +| `POST` | `/properties/{propertyId}/rate-plans/{ratePlanId}/calendar` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:126` (`upsertCalendar`) | +| `DELETE` | `/properties/{propertyId}/rate-plans/{ratePlanId}/calendar/{rateDate}` | `src/main/kotlin/com/android/trisolarisserver/controller/rate/RatePlans.kt:204` (`deleteCalendar`) | + +## Cancellation Policy + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/cancellation-policy` | `src/main/kotlin/com/android/trisolarisserver/controller/property/CancellationPolicies.kt:33` (`get`) | +| `PUT` | `/properties/{propertyId}/cancellation-policy` | `src/main/kotlin/com/android/trisolarisserver/controller/property/CancellationPolicies.kt:52` (`upsert`) | + +## Transport Modes + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/properties/{propertyId}/transport-modes` | `src/main/kotlin/com/android/trisolarisserver/controller/transport/TransportModes.kt:25` (`list`) | + +## Inbound Email + +| Method | Path | Handler | +|---|---|---| +| `POST` | `/properties/{propertyId}/inbound-emails/manual` | `src/main/kotlin/com/android/trisolarisserver/controller/email/InboundEmailManual.kt:39` (`uploadManualPdf`) | +| `GET` | `/properties/{propertyId}/inbound-emails/{emailId}/file` | `src/main/kotlin/com/android/trisolarisserver/controller/email/InboundEmails.kt:31` (`downloadEmailPdf`) | + +## Assets & Metadata + +| Method | Path | Handler | +|---|---|---| +| `GET` | `/amenities` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:39` (`listAmenities`) | +| `POST` | `/amenities` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:47` (`createAmenity`) | +| `DELETE` | `/amenities/{amenityId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:89` (`deleteAmenity`) | +| `PUT` | `/amenities/{amenityId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomAmenities.kt:67` (`updateAmenity`) | +| `GET` | `/icons/png` | `src/main/kotlin/com/android/trisolarisserver/controller/assets/IconFiles.kt:22` (`listPng`) | +| `GET` | `/icons/png/{filename}` | `src/main/kotlin/com/android/trisolarisserver/controller/assets/IconFiles.kt:38` (`getPng`) | +| `GET` | `/image-tags` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:34` (`listTags`) | +| `POST` | `/image-tags` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:41` (`createTag`) | +| `DELETE` | `/image-tags/{tagId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:71` (`deleteTag`) | +| `PUT` | `/image-tags/{tagId}` | `src/main/kotlin/com/android/trisolarisserver/controller/room/RoomImageTags.kt:55` (`updateTag`) |