property-service
Companion: README · 02 Enterprise Architecture §3 · 05 API Design · 06 Data Models §4.3–4.4 · 07 Security & Tenancy · Bundle
1. Identity
| Field | Value |
|---|---|
| Service name | property-service |
| Workspace package | @ghasi/service-property |
| Bounded context | Property (Core) |
| Domain class | Core |
| Owning team | PMS (Property Management System squad) |
| On-call tier | Tier 2 |
| Phase | Phase 0 (foundation; required for tenant onboarding, search, and booking) |
2. Purpose
property-service is the system of record for the physical things a hotel sells: the property itself (a hotel, guesthouse, or melmastoon), its room types, individual rooms, amenities, photos, and policies. It owns the geo-coordinates that drive the consumer meta-layer Leaflet map, the multi-language address and name fields that support Pashto / Dari / Persian / Tajik / English, and the amenity catalog that includes regionally meaningful items (halal kitchen, prayer room, women-only floor, generator backup, hot-water schedule).
It does not own pricing (pricing-service), per-night availability (inventory-service), live housekeeping state (housekeeping-service), maintenance work orders (maintenance-service), reservations (reservation-service), or theme visuals (theme-config-service). Room physical state (clean / dirty / OOO / OOS) is owned here as the slow-changing reference; the operational turnover lifecycle is owned by housekeeping-service and informs this service via events.
3. Aggregates Owned
| Aggregate | Lifecycle | ID prefix |
|---|---|---|
Property | draft → published → unpublished → archived | ppt_ |
RoomType | draft → active → archived | rmt_ |
Room | active → out_of_order / out_of_service → returned | rmu_ |
Amenity (catalog reference) | active → deprecated | amn_ |
Photo (media ref) | uploaded → ready → archived | pht_ |
Policy (per-property override) | draft → active → archived | pol_ |
BedConfig (value object on RoomType) | n/a | — |
RoomGroup (floor / building / wing) | active → archived | rgp_ |
4. Primary APIs
REST under /api/v1:
POST /api/v1/properties·GET /api/v1/properties·GET /api/v1/properties/{id}·PATCH /api/v1/properties/{id}·POST /api/v1/properties/{id}/publish·POST /api/v1/properties/{id}/unpublish·DELETE /api/v1/properties/{id}POST /api/v1/properties/{id}/room-types·GET …/room-types·PATCH …/room-types/{rmt}·DELETE …/room-types/{rmt}POST /api/v1/properties/{id}/rooms·POST …/rooms/bulk·GET …/rooms·PATCH …/rooms/{rmu}·POST …/rooms/{rmu}/take-out-of-order·POST …/rooms/{rmu}/return-to-service·DELETE …/rooms/{rmu}POST /api/v1/properties/{id}/photos(signed-URL request) ·PATCH …/photos/{pht}/order·DELETE …/photos/{pht}PUT /api/v1/properties/{id}/amenities·PUT …/policies·PUT …/policies/overrides/{kind}GET /api/v1/properties/geo/search?bbox=…·GET /api/v1/properties/geo/nearby?lat=…&lng=…&radiusKm=…POST /api/v1/properties/{id}/room-groups·GET …/room-groups
Internal sync endpoints under /internal/v1/property/changes consumed by sync-service. See API_CONTRACTS.
5. Events
Published (melmastoon.property.*.v1):
melmastoon.property.created.v1·melmastoon.property.updated.v1melmastoon.property.published.v1·melmastoon.property.unpublished.v1·melmastoon.property.deleted.v1melmastoon.property.room_type.created.v1·melmastoon.property.room_type.updated.v1·melmastoon.property.room_type.archived.v1melmastoon.property.room.created.v1·melmastoon.property.room.updated.v1·melmastoon.property.room.taken_out_of_order.v1·melmastoon.property.room.returned_to_service.v1·melmastoon.property.room.archived.v1melmastoon.property.amenity_set.updated.v1melmastoon.property.policy.updated.v1melmastoon.property.photo.added.v1·melmastoon.property.photo.removed.v1·melmastoon.property.photo.order_changed.v1melmastoon.property.room_group.changed.v1
Consumed:
melmastoon.tenant.created.v1— gate property creation (provision tenant context).melmastoon.tenant.deleted.v1— cascade unpublish + soft-delete of all properties/rooms.melmastoon.housekeeping.room.maintenance_required.v1— auto-OOO with reason.melmastoon.maintenance.work_order.completed.v1— auto-RTS when ticket closes and policy permits.melmastoon.file_storage.media.scanned.v1— flip Photouploaded → readywhen virus scan passes.melmastoon.lock_integration.device.paired.v1— bindlockDeviceIdonto Room.
6. Storage
- Primary: Cloud SQL Postgres, shared schema,
tenant_idRLS on every multi-tenant table. PostGIS extension enabled (geography(POINT, 4326)for property location, GIST index for bounding-box and nearby queries). - Per-language fields (name, description, address) stored as
jsonbkeyed by BCP-47 locale ({ "en": "...", "ps": "...", "fa": "...", "tg": "..." }). - Cache: Memorystore (Redis) for hot reads (
property:{id},property:{id}:rooms,property:{id}:amenities, geo-bounding-box result fragments). - Photos: binary lives in
file-storage-service(Cloud Storage); onlyMediaRefis stored here.
7. Multi-Tenancy
Every Property, RoomType, Room, Photo, Policy, RoomGroup row carries tenant_id. RLS policy <table>_tenant_isolation filters every query against current_setting('app.tenant_id')::uuid. Cross-tenant queries are rejected at the domain layer (CrossTenantReferenceError); the only legitimate cross-tenant read of property data is the projection consumed by search-aggregation-service.
8. Hotel-Specific Behaviors
- Multi-language: All guest-visible string fields require at least one language in
enabledLocales(defaults fromtenant-service); RTL-aware locales (ar,ps,fa,ur,he) are rendered RTL by the BFFs without further per-property configuration. - Regionally meaningful amenities: canonical amenity codes include
halal_kitchen,prayer_room,women_only_floor,generator_backup,hot_water_24h,hot_water_scheduled,family_room,male_dorm,female_dorm,bus_pickup,borderless_payment, alongside global amenities (wifi,parking,breakfast,pool,spa). - Address shape: structured
AddressLinewith optionalnativeScriptLine1/2for Pashto/Dari/Persian/Tajik native script; geo-coords are mandatory at publish time (validated against country bounding box). - Policy inheritance: tenant defaults (in
tenant-service) → property override (here) → rate-plan override (inpricing-service). Stored as a flat overrides map keyed bykind(check_in_time,check_out_time,cancellation,child,smoking,pets,deposit,id_required).
9. Edge Cases (top-of-mind)
- Publishing a property with zero rooms is blocked (
MELMASTOON.PROPERTY.NO_ROOMS_FOR_PUBLISH). - Duplicate room number within a property is blocked at the domain and DB layers (
UNIQUE (property_id, number)). - Deleting a room with active reservations is blocked (
MELMASTOON.PROPERTY.ROOM_HAS_ACTIVE_RESERVATIONS); operator must reaccommodate viareservation-servicefirst. - Multi-language validation: a
publishedproperty requiresnameanddescriptionindefaultLocale. - Photo ordering: explicit
orderinteger per photo; reorder is a single PATCH with full ordering vector. - Amenity de-duplication: amenity codes normalized lowercase + checked against canonical registry; unknown codes rejected.
- OOO with active reservation: setting a Room to OOO when a reservation is checked-in raises
MELMASTOON.PROPERTY.ROOM_OCCUPIED; the operator must first re-accommodate.
10. Sync Contract
Replicated to the Electron desktop SQLite store: Property (subset), RoomType, Room, Policy (resolved overrides), RoomGroup. Per-aggregate conflict policy:
| Aggregate | Policy |
|---|---|
Property metadata | server_authoritative |
RoomType | server_authoritative |
Room.status (OOO / RTS) | lww+diff by updatedAt (LWW with policy gate against transitions blocked by reservation state) |
Room other fields | server_authoritative |
Photo order / set | server_authoritative |
Policy overrides | server_authoritative |
See SYNC_CONTRACT.
11. AI Touchpoints
All AI calls go through ai-orchestrator-service via ports/AIClient. Capabilities:
property.description.draft— multilingual description draft from a structured fact sheet (HITL-gated before publish).property.photo.tag— vision auto-tagging suggesting amenities androom_typematches; HITL accept-only.property.amenity.suggest— suggest amenities given uploaded photos; HITL accept-only.property.address.geocode_fallback— geocode using a ranked provider list when manual coords are missing (provenance recorded; flagged for human review).
Every AI artifact persists with AIProvenance. See AI_INTEGRATION.
12. Failure Posture
- Photo upload failures degrade to "publish without that photo" with a warning surfaced to the operator.
- Geocode failures fall back to the manual coordinates field; publish blocked if neither is present.
- Cross-service waits (file-storage virus scan, lock-integration pairing) are async via events; the property write completes regardless and reconciles when events arrive.
13. Consumers (SLO Perspective)
| Consumer | Read pattern | Latency target | Staleness tolerance |
|---|---|---|---|
inventory-service | event-driven on room created/archived | n/a | ≤ 2 s after event |
search-aggregation-service | event-driven projection | n/a | ≤ 5 s after publish |
bff-tenant-booking-service | hot read (/properties/{id}, photos, amenities) | p95 100 ms | ≤ 60 s |
bff-consumer-service | indirectly via search aggregation | n/a | ≤ 60 s |
bff-backoffice-service (Electron) | hot reads + sync | p95 80 ms | offline-tolerant |
housekeeping-service | reads room set on tenant onboarding | p95 80 ms | fresh |
lock-integration-service | reads lockDeviceId per Room | p95 80 ms | fresh |
14. Ownership
- Team: PMS squad.
- Escalation: pms-oncall → platform-oncall.
- Reviewers: PMS architect, search architect (for projection contract), security reviewer (RLS, photo signed URLs).
15. Related Documents
- SERVICE_OVERVIEW, DOMAIN_MODEL, API_CONTRACTS, EVENT_SCHEMAS, DATA_MODEL, SYNC_CONTRACT, AI_INTEGRATION, SECURITY_MODEL, OBSERVABILITY, TESTING_STRATEGY, DEPLOYMENT_TOPOLOGY, FAILURE_MODES, LOCAL_DEV_SETUP, SERVICE_READINESS, SERVICE_RISK_REGISTER, MIGRATION_PLAN.
- 02 Enterprise Architecture, 04 Event-Driven Architecture, 05 API Design, 06 Data Models, 07 Security & Tenancy, Naming, Error Codes.