Skip to main content

property-service

Companion: README · 02 Enterprise Architecture §3 · 05 API Design · 06 Data Models §4.3–4.4 · 07 Security & Tenancy · Bundle

1. Identity

FieldValue
Service nameproperty-service
Workspace package@ghasi/service-property
Bounded contextProperty (Core)
Domain classCore
Owning teamPMS (Property Management System squad)
On-call tierTier 2
PhasePhase 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

AggregateLifecycleID prefix
Propertydraft → published → unpublished → archivedppt_
RoomTypedraft → active → archivedrmt_
Roomactive → out_of_order / out_of_service → returnedrmu_
Amenity (catalog reference)active → deprecatedamn_
Photo (media ref)uploaded → ready → archivedpht_
Policy (per-property override)draft → active → archivedpol_
BedConfig (value object on RoomType)n/a
RoomGroup (floor / building / wing)active → archivedrgp_

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.v1
  • melmastoon.property.published.v1 · melmastoon.property.unpublished.v1 · melmastoon.property.deleted.v1
  • melmastoon.property.room_type.created.v1 · melmastoon.property.room_type.updated.v1 · melmastoon.property.room_type.archived.v1
  • melmastoon.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.v1
  • melmastoon.property.amenity_set.updated.v1
  • melmastoon.property.policy.updated.v1
  • melmastoon.property.photo.added.v1 · melmastoon.property.photo.removed.v1 · melmastoon.property.photo.order_changed.v1
  • melmastoon.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 Photo uploaded → ready when virus scan passes.
  • melmastoon.lock_integration.device.paired.v1 — bind lockDeviceId onto Room.

6. Storage

  • Primary: Cloud SQL Postgres, shared schema, tenant_id RLS 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 jsonb keyed 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); only MediaRef is 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 from tenant-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 AddressLine with optional nativeScriptLine1/2 for 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 (in pricing-service). Stored as a flat overrides map keyed by kind (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 via reservation-service first.
  • Multi-language validation: a published property requires name and description in defaultLocale.
  • Photo ordering: explicit order integer 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:

AggregatePolicy
Property metadataserver_authoritative
RoomTypeserver_authoritative
Room.status (OOO / RTS)lww+diff by updatedAt (LWW with policy gate against transitions blocked by reservation state)
Room other fieldsserver_authoritative
Photo order / setserver_authoritative
Policy overridesserver_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 and room_type matches; 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)

ConsumerRead patternLatency targetStaleness tolerance
inventory-serviceevent-driven on room created/archivedn/a≤ 2 s after event
search-aggregation-serviceevent-driven projectionn/a≤ 5 s after publish
bff-tenant-booking-servicehot read (/properties/{id}, photos, amenities)p95 100 ms≤ 60 s
bff-consumer-serviceindirectly via search aggregationn/a≤ 60 s
bff-backoffice-service (Electron)hot reads + syncp95 80 msoffline-tolerant
housekeeping-servicereads room set on tenant onboardingp95 80 msfresh
lock-integration-servicereads lockDeviceId per Roomp95 80 msfresh

14. Ownership

  • Team: PMS squad.
  • Escalation: pms-oncall → platform-oncall.
  • Reviewers: PMS architect, search architect (for projection contract), security reviewer (RLS, photo signed URLs).