Construire le domaine métier principal de la plateforme :
Property PropertyType PropertyAmenity PropertyImage PropertyAvailability PropertyPricing PropertyPolicy PropertyReview
afin de permettre :
Catalogue de propriétés Gestion des disponibilités Gestion tarifaire Avis clients Moteur de réservation Recherche avancée Revenue Management Channel Management Ready
Property ├── PropertyType ├── PropertyAmenity ├── PropertyImage ├── PropertyAvailability ├── PropertyPricing ├── PropertyPolicy └── PropertyReview ↓ Reservation Engine ↓ CRM ↓ Revenue Engine
Modèle Property Core
Property PropertyType PropertyAmenity Relations Multi-tenant
Catalogue des Propriétés
PropertyModule CRUD Recherche Pagination Filtres
Gestion des Équipements
Amenities Catégories Recherche Attribution
Gestion des Images
PropertyImage Upload Galerie Ordonnancement MinIO/S3
Disponibilités
PropertyAvailability Calendrier Blocages Inventaire
Tarification
PropertyPricing Tarifs saisonniers Tarifs dynamiques Revenue Ready
Politiques
PropertyPolicy Annulation Check-in Check-out Règlement
Avis Clients
PropertyReview Notation Modération Réputation
Recherche Avancée
Recherche géographique Disponibilités Prix Filtres avancés
Analytics & Intelligence
Performance propriétés Occupation Revenue Recommandations
src/modules property property-types property-amenities property-images property-availability property-pricing property-policies property-reviews property-search property-analytics
Property PropertyType PropertyAmenity PropertyImage PropertyAvailability PropertyPricing PropertyPolicy PropertyReview
✓ Multi-tenant ✓ AuditLog ✓ CRM Integration ✓ Reservation Ready ✓ Revenue Ready ✓ Analytics Ready ✓ Search Ready ✓ AI Ready ✓ Channel Manager Ready
Le Sprint 4 est terminé lorsque :
✓ Catalogue complet ✓ Disponibilités ✓ Tarification ✓ Politiques ✓ Avis ✓ Recherche ✓ Analytics ✓ Réservation Ready
Créer les fondations du domaine métier Property.
À l'issue de cette étape :
✓ Property ✓ PropertyType ✓ PropertyAmenity ✓ Multi-tenant ✓ Catalogue Ready ✓ Reservation Ready ✓ CRM Ready ✓ Analytics Ready
Property ├── PropertyType ├── PropertyAmenity ├── PropertyImage ├── PropertyAvailability ├── PropertyPricing ├── PropertyPolicy └── PropertyReview ↓ Reservation Engine ↓ Revenue Engine
model PropertyType {
id String
@id
@default(uuid())
tenantId String
code String
name String
description String?
icon String?
active Boolean
@default(true)
displayOrder Int
@default(0)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
properties Property[]
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([active])
}
HOTEL_ROOM APARTMENT HOUSE VILLA STUDIO HOSTEL RESORT CHALET CAMPING OTHER
model PropertyAmenity {
id String
@id
@default(uuid())
tenantId String
code String
name String
description String?
category String?
icon String?
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
properties PropertyAmenityAssignment[]
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([category])
@@index([active])
}
model PropertyAmenityAssignment {
id String
@id
@default(uuid())
propertyId String
amenityId String
createdAt DateTime
@default(now())
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
amenity PropertyAmenity
@relation(
fields:[amenityId],
references:[id],
onDelete:Cascade
)
@@unique([
propertyId,
amenityId
])
@@index([propertyId])
@@index([amenityId])
}
GENERAL KITCHEN BATHROOM BEDROOM CONNECTIVITY SECURITY ACCESSIBILITY ENTERTAINMENT OUTDOOR PARKING
model Property {
id String
@id
@default(uuid())
tenantId String
propertyTypeId String
propertyNumber String
externalReference String?
name String
slug String
description String?
shortDescription String?
active Boolean
@default(true)
featured Boolean
@default(false)
published Boolean
@default(false)
maxGuests Int
@default(1)
bedrooms Int
@default(0)
bathrooms Int
@default(0)
beds Int
@default(1)
floorArea Decimal?
@db.Decimal(10,2)
floorAreaUnit String?
@default("m²")
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
propertyType PropertyType
@relation(
fields:[propertyTypeId],
references:[id]
)
amenities PropertyAmenityAssignment[]
@@unique([
tenantId,
propertyNumber
])
@@unique([
tenantId,
slug
])
@@index([tenantId])
@@index([propertyTypeId])
@@index([active])
@@index([published])
@@index([featured])
}
minimumStay Int?
@default(1)
maximumStay Int?
advanceBookingDays Int?
instantBooking Boolean
@default(false)
ownerName String? ownerEmail String? ownerPhone String?
viewCount Int
@default(0)
bookingCount Int
@default(0)
averageRating Float?
model PropertyAddress {
id String
@id
@default(uuid())
propertyId String
@unique
addressLine1 String
addressLine2 String?
postalCode String?
city String
region String?
country String
latitude Decimal?
@db.Decimal(10,7)
longitude Decimal?
@db.Decimal(10,7)
timezone String?
geoValidated Boolean
@default(false)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([city])
@@index([country])
@@index([latitude])
@@index([longitude])
}
address PropertyAddress?
seoTitle String? seoDescription String? seoKeywords String?
publishedAt DateTime? publishedBy String?
visibility PropertyVisibility
@default(PRIVATE)
enum PropertyVisibility {
PRIVATE
INTERNAL
PUBLIC
}
Compatible avec :
AuditLog PropertyHistory Reservation ChannelManager
metadata Json?
Dans le seed :
HOTEL_ROOM APARTMENT HOUSE VILLA STUDIO
WIFI AIR_CONDITIONING PARKING POOL TV KITCHEN WASHING_MACHINE BREAKFAST PET_FRIENDLY
npx prisma migrate dev \
--name property_core
npx prisma generate
Property PropertyType PropertyAmenity PropertyAmenityAssignment PropertyAddress
présents dans Prisma Client.
Le Sprint 4-A.1 est terminé lorsque :
✓ Property créé ✓ PropertyType créé ✓ PropertyAmenity créé ✓ PropertyAmenityAssignment créé ✓ PropertyAddress créé ✓ Multi-tenant prêt ✓ Catalogue prêt ✓ Réservation prête ✓ Analytics prêt
Property PropertyType PropertyAmenity PropertyAmenityAssignment PropertyAddress PropertyVisibility
Transformer le modèle Property en véritable référentiel immobilier Enterprise.
À l'issue de cette étape :
✓ PropertyStatus ✓ PropertyCategory ✓ PropertyOwner ✓ PropertyManager ✓ PropertyFeatures ✓ PropertyCompliance ✓ PropertyScore ✓ PropertyLifecycle ✓ Revenue Ready ✓ Enterprise Ready
Property ├── PropertyType ├── PropertyCategory ├── PropertyOwner ├── PropertyManager ├── PropertyFeatures ├── PropertyCompliance ├── PropertyScore └── PropertyLifecycle ↓ Reservation ↓ Revenue ↓ Analytics ↓ Compliance
enum PropertyStatus {
DRAFT
PENDING_REVIEW
ACTIVE
INACTIVE
SUSPENDED
ARCHIVED
}
status PropertyStatus
@default(DRAFT)
activatedAt DateTime? deactivatedAt DateTime? archivedAt DateTime?
model PropertyCategory {
id String
@id
@default(uuid())
tenantId String
code String
name String
description String?
parentCategoryId String?
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
parentCategory PropertyCategory?
@relation(
"PropertyCategoryTree",
fields:[parentCategoryId],
references:[id]
)
children PropertyCategory[]
@relation(
"PropertyCategoryTree"
)
properties Property[]
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([parentCategoryId])
}
propertyCategoryId String?
propertyCategory PropertyCategory?
@relation(
fields:[propertyCategoryId],
references:[id]
)
LUXURY BUSINESS FAMILY BUDGET LONG_STAY SHORT_STAY PREMIUM CORPORATE
model PropertyOwner {
id String
@id
@default(uuid())
tenantId String
code String
companyName String?
firstName String?
lastName String?
email String?
phone String?
taxNumber String?
active Boolean
@default(true)
metadata Json?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
properties Property[]
@@unique([
tenantId,
code
])
@@index([tenantId])
}
propertyOwnerId String?
propertyOwner PropertyOwner?
@relation(
fields:[propertyOwnerId],
references:[id]
)
model PropertyManager {
id String
@id
@default(uuid())
tenantId String
userId String?
code String
name String
email String?
phone String?
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
properties Property[]
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([userId])
}
propertyManagerId String?
propertyManager PropertyManager?
@relation(
fields:[propertyManagerId],
references:[id]
)
model PropertyFeature {
id String
@id
@default(uuid())
tenantId String
code String
name String
category String?
active Boolean
@default(true)
createdAt DateTime
@default(now())
properties PropertyFeatureAssignment[]
@@unique([
tenantId,
code
])
@@index([tenantId])
}
model PropertyFeatureAssignment {
id String
@id
@default(uuid())
propertyId String
featureId String
value String?
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
feature PropertyFeature
@relation(
fields:[featureId],
references:[id],
onDelete:Cascade
)
@@unique([
propertyId,
featureId
])
}
SEA_VIEW MOUNTAIN_VIEW BALCONY TERRACE PRIVATE_POOL JACUZZI SMART_LOCK EV_CHARGER
model PropertyCompliance {
id String
@id
@default(uuid())
propertyId String
@unique
licenseNumber String?
registrationNumber String?
insurancePolicy String?
insuranceExpiresAt DateTime?
fireSafetyValidated Boolean
@default(false)
accessibilityValidated Boolean
@default(false)
complianceScore Int
@default(0)
lastAuditAt DateTime?
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
}
compliance PropertyCompliance?
model PropertyScore {
id String
@id
@default(uuid())
propertyId String
@unique
qualityScore Int
@default(0)
reviewScore Int
@default(0)
occupancyScore Int
@default(0)
revenueScore Int
@default(0)
complianceScore Int
@default(0)
globalScore Int
@default(0)
calculatedAt DateTime
@default(now())
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
}
score PropertyScore?
model PropertyLifecycle {
id String
@id
@default(uuid())
propertyId String
@unique
onboardingCompleted Boolean
@default(false)
contentCompleted Boolean
@default(false)
pricingCompleted Boolean
@default(false)
availabilityCompleted Boolean
@default(false)
complianceCompleted Boolean
@default(false)
publishedCompleted Boolean
@default(false)
launchDate DateTime?
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
}
lifecycle PropertyLifecycle?
occupancyRate Float?
revenueYtd Decimal?
@db.Decimal(14,2)
averageNightlyRate Decimal?
@db.Decimal(12,2)
revPar Decimal?
@db.Decimal(12,2)
Compatible avec :
ADR RevPAR Occupancy Yield Management
Catégories :
LUXURY BUSINESS FAMILY PREMIUM LONG_STAY
npx prisma migrate dev \
--name property_enterprise
npx prisma generate
Compatible avec :
PropertyHistory AuditLog Reservation ChannelManager RevenueManagement
Le Sprint 4-A.2 est terminé lorsque :
✓ PropertyStatus ✓ PropertyCategory ✓ PropertyOwner ✓ PropertyManager ✓ PropertyFeature ✓ PropertyCompliance ✓ PropertyScore ✓ PropertyLifecycle ✓ Revenue KPI ✓ Analytics Ready
PropertyCategory PropertyOwner PropertyManager PropertyFeature PropertyFeatureAssignment PropertyCompliance PropertyScore PropertyLifecycle PropertyStatus
Implémenter le premier module métier complet du domaine Property.
À l'issue de cette étape :
✓ PropertyModule ✓ PropertyController ✓ PropertyService ✓ CRUD Property ✓ Recherche ✓ Pagination ✓ Multi-Tenant Security ✓ Swagger
PropertyModule ├── PropertyController ├── PropertyService ├── PropertyRepository ├── DTOs ├── Validators └── Policies ↓ Prisma ↓ Property
src/modules/property ├── application │ │ └── dto │ │ ├── create-property.dto.ts │ │ ├── update-property.dto.ts │ │ ├── property-query.dto.ts │ │ └── property-response.dto.ts │ ├── domain │ │ └── services │ │ └── property.service.ts │ ├── presentation │ │ └── controllers │ │ └── property.controller.ts │ ├── property.module.ts │ └── property.constants.ts
@Module({
imports: [
PrismaModule,
AuthModule,
AuditModule
],
controllers: [
PropertyController
],
providers: [
PropertyService
],
exports: [
PropertyService
]
})
export class PropertyModule {}
create-property.dto.ts
export class CreatePropertyDto {
propertyTypeId: string;
propertyNumber: string;
name: string;
slug: string;
description?: string;
shortDescription?: string;
maxGuests?: number;
bedrooms?: number;
bathrooms?: number;
beds?: number;
instantBooking?: boolean;
ownerName?: string;
ownerEmail?: string;
ownerPhone?: string;
}
export class UpdatePropertyDto
extends PartialType(
CreatePropertyDto
) {}
export class PropertyQueryDto {
search?: string;
propertyTypeId?: string;
propertyCategoryId?: string;
status?: string;
published?: boolean;
featured?: boolean;
page?: number = 1;
limit?: number = 25;
sortBy?: string = 'createdAt';
sortOrder?: 'asc' | 'desc'
= 'desc';
}
property.service.ts
findAll() findById() create() update() remove()
async create(
tenantId: string,
dto: CreatePropertyDto,
userId: string,
) {
return this.prisma.property.create({
data: {
tenantId,
...dto
}
});
}
tenantId
sur toutes les requêtes.
return this.prisma.property.findFirst({
where: {
id,
tenantId
}
});
Property Tenant Permissions
avant modification.
deletedAt: new Date() status: PropertyStatus.ARCHIVED
name description propertyNumber slug
OR: [
{
name: {
contains: search,
mode: 'insensitive'
}
},
{
propertyNumber: {
contains: search,
mode: 'insensitive'
}
}
]
PropertyType Category Status Published Featured
include: {
propertyType: true,
propertyCategory: true,
address: true
}
const skip = (page - 1) * limit;
{
"items": [],
"meta": {
"page": 1,
"limit": 25,
"total": 150
}
}
name createdAt updatedAt averageRating bookingCount
property.controller.ts
@ApiTags('Properties')
@ApiBearerAuth()
@Controller('properties')
@UseGuards(
JwtAuthGuard
)
GET /properties
property.read
GET /properties/{id}
POST /properties
property.create
PUT /properties/{id}
property.update
DELETE /properties/{id}
property.delete
Toutes les requêtes :
WHERE tenantId = currentTenant
Cross Tenant Access
avec :
ForbiddenException
PROPERTY_CREATED PROPERTY_UPDATED PROPERTY_DELETED PROPERTY_VIEWED
Properties Create Property Update Property Delete Property Search Properties
{
"propertyTypeId":"uuid",
"propertyNumber":"APT-001",
"name":"Appartement Centre Ville",
"slug":"appartement-centre-ville",
"maxGuests":4
}
property.read property.create property.update property.delete property.publish property.manage
SUPER_ADMIN TENANT_ADMIN PROPERTY_MANAGER RECEPTIONIST
Préparer les relations futures :
PropertyAvailability PropertyPricing PropertyImage PropertyReview Reservation
Le Sprint 4-B.1 est terminé lorsque :
✓ PropertyModule créé ✓ PropertyController créé ✓ PropertyService créé ✓ CRUD complet ✓ Recherche ✓ Pagination ✓ Multi-tenant Security ✓ RBAC ✓ AuditLog ✓ Swagger
PropertyModule PropertyController PropertyService CreatePropertyDto UpdatePropertyDto PropertyQueryDto PropertyResponseDto
Transformer le catalogue Property en moteur de recherche immobilier Enterprise.
À l'issue de cette étape :
✓ Filtres multicritères ✓ Recherche géographique ✓ Recherche par capacité ✓ Recherche par équipements ✓ Recherche par score ✓ Recherches sauvegardées ✓ Catalogue intelligent ✓ Property Discovery Engine
Property Catalog ↓ Search Engine ├── Geographic Search ├── Capacity Search ├── Amenity Search ├── Score Search ├── Availability Search └── Smart Ranking ↓ Catalog Intelligence ↓ Reservation Engine
model PropertySavedSearch {
id String
@id
@default(uuid())
tenantId String
userId String
name String
query String?
filters Json
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
user User
@relation(
fields:[userId],
references:[id]
)
@@index([tenantId])
@@index([userId])
}
model PropertySearchHistory {
id String
@id
@default(uuid())
tenantId String
userId String?
searchTerm String?
filters Json?
resultsCount Int
@default(0)
createdAt DateTime
@default(now())
@@index([tenantId])
@@index([createdAt])
}
searchScore Float?
npx prisma migrate dev \
--name property_catalog_search
property-search.dto.ts
export class PropertySearchDto {
search?: string;
propertyTypeId?: string;
propertyCategoryId?: string;
city?: string;
country?: string;
minGuests?: number;
maxGuests?: number;
minBedrooms?: number;
maxBedrooms?: number;
amenities?: string[];
minScore?: number;
maxScore?: number;
featured?: boolean;
page?: number = 1;
limit?: number = 25;
}
property-catalog.service.ts
search() advancedSearch() searchByLocation() searchByAmenities() searchByCapacity()
Type Catégorie Ville Pays Statut Publication Score Capacité
where: {
tenantId,
active: true,
city,
maxGuests: {
gte: minGuests
}
}
Appartement + Paris + 4 personnes + Wifi + Score > 80
PropertyAddress Latitude Longitude
GET /properties/search/radius
latitude longitude radiusKm
Formule :
Haversine
pour calculer la distance.
{
"property":"Villa Prestige",
"distanceKm":4.2
}
Guests Bedrooms Bathrooms Beds
8 voyageurs 4 chambres 3 salles de bain
Toutes les propriétés compatibles.
Wifi Piscine Parking Cuisine Climatisation Jacuzzi
amenities: {
some: {
amenity: {
code: 'WIFI'
}
}
}
ANY ALL
pour les équipements.
PropertyScore
GlobalScore ReviewScore RevenueScore OccupancyScore
Score > 90
Propriétés Premium.
property-saved-search.service.ts
saveSearch() listSearches() executeSearch() deleteSearch()
GET /properties/search/saved
POST /properties/search/saved
DELETE /properties/search/saved/{id}
Paris Premium Wifi Piscine 6 personnes
Mes Villas Premium
property-ranking.service.ts
ReviewScore Occupancy Revenue Featured SearchPopularity
40% Reviews 20% Revenue 20% Occupation 20% Popularité
getRecommendedProperties()
Historique recherche Réservations Préférences utilisateur
GET /properties/search
GET /properties/search/radius
GET /properties/search/facets
GET /properties/search/saved
POST /properties/search/saved
DELETE /properties/search/saved/{id}
{
"cities": {
"Paris": 142,
"Lyon": 38
},
"types": {
"APARTMENT": 97,
"VILLA": 31
}
}
PROPERTY_SEARCH PROPERTY_RADIUS_SEARCH PROPERTY_SAVED_SEARCH PROPERTY_SEARCH_EXECUTED
property.search property.search.saved property.search.analytics
Compatible avec :
PropertyAvailability Reservation Calendar Engine Inventory Engine
Compatible avec :
PropertyPricing Dynamic Pricing Revenue Management
Compatible avec :
Enterprise Catalog Recommendation Engine Search Intelligence Booking Engine
Le Sprint 4-B.2 est terminé lorsque :
✓ Filtres multicritères ✓ Recherche géographique ✓ Recherche capacité ✓ Recherche équipements ✓ Recherche score ✓ Recherches sauvegardées ✓ Catalogue intelligent ✓ API Catalogue ✓ Audit intégré
PropertyCatalogService PropertySavedSearch PropertySearchHistory PropertyRankingService PropertySearchDto Enterprise Property Search API
Implémenter la gestion complète des équipements des propriétés.
À l'issue de cette étape :
✓ PropertyAmenityModule ✓ PropertyAmenityController ✓ PropertyAmenityService ✓ CRUD Amenities ✓ Catégories ✓ Attribution aux propriétés ✓ Recherche équipements ✓ Catalogue enrichi
PropertyAmenity ↓ PropertyAmenityAssignment ↓ Property ↓ Property Search ↓ Reservation Engine
src/modules/property-amenities ├── application │ │ └── dto │ │ ├── create-property-amenity.dto.ts │ │ ├── update-property-amenity.dto.ts │ │ ├── property-amenity-query.dto.ts │ │ └── assign-property-amenity.dto.ts │ ├── domain │ │ └── services │ │ └── property-amenity.service.ts │ ├── presentation │ │ └── controllers │ │ └── property-amenity.controller.ts │ └── property-amenity.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
AuditModule
],
controllers: [
PropertyAmenityController
],
providers: [
PropertyAmenityService
],
exports: [
PropertyAmenityService
]
})
export class PropertyAmenityModule {}
export class CreatePropertyAmenityDto {
code: string;
name: string;
description?: string;
category?: string;
icon?: string;
active?: boolean;
}
export class UpdatePropertyAmenityDto
extends PartialType(
CreatePropertyAmenityDto
) {}
export class AssignPropertyAmenityDto {
amenityIds: string[];
}
export class PropertyAmenityQueryDto {
search?: string;
category?: string;
active?: boolean;
page?: number = 1;
limit?: number = 25;
}
property-amenity.service.ts
findAll() findById() create() update() remove() assignToProperty() removeFromProperty() listPropertyAmenities()
async create(
tenantId: string,
dto: CreatePropertyAmenityDto,
) {
return this.prisma.propertyAmenity.create({
data: {
tenantId,
...dto
}
});
}
Tenant Code unique Permissions
avant modification.
active: false
afin de conserver l'historique.
GENERAL KITCHEN BATHROOM BEDROOM CONNECTIVITY SECURITY ACCESSIBILITY ENTERTAINMENT OUTDOOR PARKING
GET /property-amenities/categories
[ "GENERAL", "KITCHEN", "BATHROOM", "CONNECTIVITY" ]
Nombre d'équipements par catégorie.
PropertyAmenityAssignment
await prisma.propertyAmenityAssignment.create({
data: {
propertyId,
amenityId
}
});
Wifi Parking Piscine Climatisation
en une seule requête.
syncPropertyAmenities()
pour remplacer totalement la liste.
Code Nom Description Catégorie
OR: [
{
name: {
contains: search,
mode: 'insensitive'
}
},
{
code: {
contains: search,
mode: 'insensitive'
}
}
]
Catégorie Actif Utilisation
property-amenity.controller.ts
@ApiTags( 'Property Amenities' ) @ApiBearerAuth() @Controller( 'property-amenities' ) @UseGuards( JwtAuthGuard )
GET /property-amenities
GET /property-amenities/{id}
POST /property-amenities
PUT /property-amenities/{id}
DELETE /property-amenities/{id}
POST /properties/{id}/amenities
GET /properties/{id}/amenities
DELETE /properties/{id}/amenities/{amenityId}
{
"items": [],
"meta": {
"page": 1,
"limit": 25,
"total": 93
}
}
name category createdAt
tenantId
sur toutes les opérations.
property.amenity.read property.amenity.create property.amenity.update property.amenity.delete property.amenity.assign
PROPERTY_AMENITY_CREATED PROPERTY_AMENITY_UPDATED PROPERTY_AMENITY_DELETED PROPERTY_AMENITY_ASSIGNED PROPERTY_AMENITY_REMOVED
Compatible avec :
Property Search Availability Search Booking Search Recommendation Engine
Préparer :
Most Used Amenities Amenity Popularity Amenity Conversion Rate
pour Sprint 4-J.
Le Sprint 4-C.1 est terminé lorsque :
✓ PropertyAmenityModule créé ✓ PropertyAmenityController créé ✓ PropertyAmenityService créé ✓ CRUD Amenities ✓ Catégories ✓ Attribution aux propriétés ✓ Recherche équipements ✓ Multi-tenant Security ✓ AuditLog intégré
PropertyAmenityModule PropertyAmenityController PropertyAmenityService CreatePropertyAmenityDto AssignPropertyAmenityDto PropertyAmenityAssignment API
Faire évoluer le catalogue des équipements vers un moteur intelligent capable d'alimenter :
Recherche avancée Matching réservation Scoring propriétés Recommandations IA Revenue Management Analytics
À l'issue de cette étape :
✓ Hiérarchie équipements ✓ Tags équipements ✓ Popularité ✓ Scoring équipements ✓ Recommandations automatiques ✓ Classification IA ✓ Amenity Intelligence Engine
PropertyAmenity ↓ Amenity Intelligence ├── Hierarchy ├── Tags ├── Popularity ├── Scoring ├── Recommendations └── AI Classification ↓ Property Search ↓ Booking Engine ↓ Recommendation Engine
parentAmenityId String?
parentAmenity PropertyAmenity?
@relation(
"AmenityHierarchy",
fields:[parentAmenityId],
references:[id]
)
children PropertyAmenity[]
@relation(
"AmenityHierarchy"
)
CONNECTIVITY ├── WIFI ├── FIBER ├── ETHERNET └── WORKSPACE
OUTDOOR ├── TERRACE ├── GARDEN ├── BBQ └── PRIVATE_POOL
hierarchyLevel Int
@default(0)
model AmenityTag {
id String
@id
@default(uuid())
tenantId String
code String
name String
createdAt DateTime
@default(now())
amenities AmenityTagAssignment[]
@@unique([
tenantId,
code
])
@@index([tenantId])
}
model AmenityTagAssignment {
id String
@id
@default(uuid())
amenityId String
tagId String
amenity PropertyAmenity
@relation(
fields:[amenityId],
references:[id],
onDelete:Cascade
)
tag AmenityTag
@relation(
fields:[tagId],
references:[id],
onDelete:Cascade
)
@@unique([
amenityId,
tagId
])
}
PREMIUM LUXURY BUSINESS FAMILY ACCESSIBLE PET_FRIENDLY REMOTE_WORK ECO_FRIENDLY
usageCount Int
@default(0)
searchCount Int
@default(0)
bookingImpactScore Float?
Property Search Bookings Property Views Recommendations
Wifi Utilisé dans 94% des biens
Piscine Impact réservation +22%
model AmenityScore {
id String
@id
@default(uuid())
amenityId String
@unique
popularityScore Float
@default(0)
conversionScore Float
@default(0)
revenueScore Float
@default(0)
recommendationScore Float
@default(0)
globalScore Float
@default(0)
calculatedAt DateTime
@default(now())
amenity PropertyAmenity
@relation(
fields:[amenityId],
references:[id],
onDelete:Cascade
)
}
Popularity 30% Conversion 30% Revenue 20% Recommendation 20%
90-100 Elite 75-89 Premium 50-74 Standard 0-49 Basic
model AmenityRecommendation {
id String
@id
@default(uuid())
propertyId String
amenityId String
recommendationScore Float
reason String?
accepted Boolean?
createdAt DateTime
@default(now())
@@index([propertyId])
@@index([amenityId])
}
amenity-recommendation.service.ts
generateRecommendations() findMissingAmenities() compareSimilarProperties()
Villa Premium Sans Piscine ↓ Suggestion PRIVATE_POOL
Appartement Business Sans Wifi ↓ Suggestion WIFI
amenity-classification.service.ts
classifyAmenity() detectCategory() generateTags() detectPremiumLevel()
Borne recharge Tesla
Category: PARKING Tags: PREMIUM ECO_FRIENDLY Score: 82
Luxury Business Family Accessibility
amenity-analytics.service.ts
getMostPopularAmenities() getHighestRevenueAmenities() getConversionImpact()
Top Amenities Revenue Impact Booking Impact Search Impact
GET /property-amenities/analytics
GET /property-amenities/hierarchy GET /property-amenities/recommendations GET /property-amenities/analytics POST /property-amenities/classify
{
"amenity":"WIFI",
"globalScore":96,
"classification":"ELITE"
}
AMENITY_CLASSIFIED AMENITY_RECOMMENDED AMENITY_SCORE_UPDATED AMENITY_TAG_ASSIGNED
property.amenity.analytics property.amenity.recommend property.amenity.classify
Property Search Recommendation Engine Smart Filters
Property Analytics Revenue Analytics Property Intelligence
AI Recommendations Knowledge Graph Property Intelligence
npx prisma migrate dev \
--name amenity_intelligence
npx prisma generate
Le Sprint 4-C.2 est terminé lorsque :
✓ Hiérarchie équipements ✓ Tags équipements ✓ Popularité ✓ Scoring équipements ✓ Recommandations automatiques ✓ Classification IA ✓ Analytics équipements ✓ API Enterprise ✓ Audit intégré
AmenityTag AmenityTagAssignment AmenityScore AmenityRecommendation AmenityRecommendationService AmenityClassificationService AmenityAnalyticsService Amenity Intelligence Engine
Implémenter la gestion complète des médias immobiliers afin d'alimenter :
Catalogue Moteur de réservation Galerie Property Marketing SEO Analytics
À l'issue de cette étape :
✓ PropertyImage ✓ PropertyImageModule ✓ PropertyImageController ✓ PropertyImageService ✓ Upload ✓ Galerie ✓ Ordonnancement ✓ Multi-Tenant Security
Property ↓ PropertyImage ↓ Storage Layer (MinIO / S3) ↓ Image Processing ↓ Gallery API ↓ Property Catalog
model PropertyImage {
id String
@id
@default(uuid())
tenantId String
propertyId String
fileName String
originalFileName String
mimeType String
fileSize Int
storagePath String
publicUrl String?
title String?
altText String?
caption String?
displayOrder Int
@default(0)
isPrimary Boolean
@default(false)
width Int?
height Int?
uploadedAt DateTime
@default(now())
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
@@index([propertyId])
@@index([displayOrder])
@@index([isPrimary])
}
images PropertyImage[]
npx prisma migrate dev \
--name property_images
npx prisma generate
src/modules/property-images ├── application │ │ └── dto │ │ ├── upload-property-image.dto.ts │ │ ├── reorder-property-images.dto.ts │ │ └── property-image-response.dto.ts │ ├── domain │ │ └── services │ │ └── property-image.service.ts │ ├── presentation │ │ └── controllers │ │ └── property-image.controller.ts │ └── property-image.module.ts
@Module({
imports: [
PrismaModule,
StorageModule,
AuthModule,
AuditModule
],
controllers: [
PropertyImageController
],
providers: [
PropertyImageService
],
exports: [
PropertyImageService
]
})
export class PropertyImageModule {}
export class UploadPropertyImageDto {
title?: string;
altText?: string;
caption?: string;
isPrimary?: boolean;
}
export class ReorderPropertyImagesDto {
images: {
id: string;
order: number;
}[];
}
property-image.service.ts
findAll() upload() delete() reorder() setPrimary()
Upload ↓ Validation ↓ Storage ↓ Prisma ↓ Gallery
const image =
await storage.upload(file);
await prisma.propertyImage.create({
data: {
tenantId,
propertyId,
storagePath:
image.path,
publicUrl:
image.url
}
});
image/jpeg image/png image/webp
10 MB
GET /properties/{id}/images
[
{
"id":"uuid",
"url":"...",
"isPrimary":true
}
]
Une seule image :
isPrimary = true
par propriété.
displayOrder ASC
PUT /properties/{id}/images/order
{
"images":[
{
"id":"1",
"order":1
},
{
"id":"2",
"order":2
}
]
}
prisma.$transaction()
pour garantir la cohérence.
DELETE /properties/{id}/images/{imageId}
Prisma ↓ Storage ↓ Audit
Suppression de la dernière image principale.
property-image.controller.ts
@ApiTags( 'Property Images' ) @ApiBearerAuth() @Controller( 'properties/:id/images' ) @UseGuards( JwtAuthGuard )
GET /properties/{id}/images
POST /properties/{id}/images
PUT /properties/{id}/images/order
DELETE /properties/{id}/images/{imageId}
@UseInterceptors(
FileInterceptor('file')
)
tenantId propertyId
avant toute opération.
property.image.read property.image.upload property.image.update property.image.delete
PROPERTY_IMAGE_UPLOADED PROPERTY_IMAGE_REORDERED PROPERTY_IMAGE_DELETED PROPERTY_PRIMARY_IMAGE_CHANGED
Compatible avec :
MinIO AWS S3 CDN Thumbnail Generation Image Optimization
Compatible avec :
Property Gallery SEO Images Marketing Assets Review Images
Le Sprint 4-D.1 est terminé lorsque :
✓ PropertyImage créé ✓ PropertyImageModule créé ✓ PropertyImageController créé ✓ PropertyImageService créé ✓ Upload opérationnel ✓ Galerie opérationnelle ✓ Ordonnancement opérationnel ✓ Multi-tenant Security ✓ AuditLog intégré
PropertyImage PropertyImageModule PropertyImageController PropertyImageService UploadPropertyImageDto ReorderPropertyImagesDto Property Gallery API
Transformer la gestion d'images Property en véritable plateforme média Enterprise.
À l'issue de cette étape :
✓ MinIO / S3 ✓ CDN ✓ Thumbnails ✓ WebP ✓ Compression ✓ Watermark ✓ Vision IA ✓ Détection qualité ✓ Media Intelligence Platform
PropertyImage ↓ Media Platform ├── Storage ├── CDN ├── Image Processing ├── AI Vision ├── Optimization └── Analytics ↓ Property Catalog ↓ Reservation Platform
storageProvider String?
cdnUrl String?
thumbnailUrl String?
mediumUrl String?
largeUrl String?
webpUrl String?
checksum String?
processingStatus ImageProcessingStatus
@default(PENDING)
enum ImageProcessingStatus {
PENDING
PROCESSING
COMPLETED
FAILED
}
npx prisma migrate dev \
--name media_platform
src/modules/media ├── storage │ ├── image-processing │ ├── ai-vision │ ├── cdn │ └── analytics
storage.service.ts
upload() delete() copy() move() exists() generateSignedUrl()
MinIO AWS S3 Cloudflare R2 Azure Blob Google Cloud Storage
STORAGE_PROVIDER=minio MINIO_ENDPOINT= MINIO_BUCKET= MINIO_ACCESS_KEY= MINIO_SECRET_KEY=
cdn.service.ts
buildUrl() invalidate() purgeCache()
Cloudflare CloudFront Fastly Bunny CDN
thumbnailUrl mediumUrl largeUrl webpUrl
automatiquement.
image-processing.service.ts
Sharp
Thumbnail Medium Large WebP
Thumbnail : 300px Medium : 1200px Large : 2400px
Upload ↓ Original ↓ Resize ↓ WebP ↓ CDN ↓ Gallery
Sharp MozJPEG WebP
JPEG 80% WebP 75% PNG optimisé
Compression Ratio Storage Saved Bandwidth Saved
watermark.service.ts
Logo Texte Dynamic Branding
Position Opacity Scale Margins
Marketplace Partenaires Protection contenu
model ImageVisionAnalysis {
id String
@id
@default(uuid())
imageId String
@unique
labels Json?
objects Json?
qualityScore Float?
blurScore Float?
brightnessScore Float?
adultContentDetected Boolean
@default(false)
duplicateDetected Boolean
@default(false)
analyzedAt DateTime
@default(now())
image PropertyImage
@relation(
fields:[imageId],
references:[id],
onDelete:Cascade
)
}
vision-analysis.service.ts
AWS Rekognition Google Vision Azure Vision OpenAI Vision
Bedroom Bathroom Kitchen Pool Garden Sea View Pets
Photo ↓ Pool ↓ Amenity: PRIVATE_POOL
Sharpness Brightness Contrast Resolution Composition
90-100 Excellent 75-89 Good 50-74 Acceptable 0-49 Poor
Image sombre ↓ Augmenter luminosité
Image floue ↓ Remplacer photo
media-analytics.service.ts
Images Uploaded Storage Used Compression Ratio AI Quality Score Most Viewed Images
GET /media/analytics
POST /media/process POST /media/watermark POST /media/analyze GET /media/analytics GET /media/storage
IMAGE_UPLOADED IMAGE_PROCESSED IMAGE_ANALYZED IMAGE_WATERMARKED IMAGE_OPTIMIZED
media.read media.upload media.process media.analyze media.admin
Compatible avec :
Review Images User Uploads Moderation
Compatible avec :
Property Search AI Ranking Visual Search
Compatible avec :
Computer Vision Property Intelligence AI Classification Knowledge Graph
Compatible avec :
Enterprise Media Platform Global CDN AI Vision Digital Asset Management
Le Sprint 4-D.2 est terminé lorsque :
✓ MinIO/S3 intégré ✓ CDN intégré ✓ Variantes d'images ✓ WebP ✓ Compression ✓ Watermark ✓ Vision IA ✓ Détection qualité ✓ Analytics média ✓ Audit intégré
StorageService ImageProcessingService CDNService WatermarkService VisionAnalysisService MediaAnalyticsService ImageVisionAnalysis Enterprise Media Platform
Implémenter le moteur de disponibilités des propriétés.
Cette couche constitue la fondation du futur moteur de réservation.
À l'issue de cette étape :
✓ PropertyAvailability ✓ PropertyAvailabilityModule ✓ PropertyAvailabilityController ✓ PropertyAvailabilityService ✓ Calendrier ✓ Blocages ✓ Inventaire ✓ Reservation Ready
Property ↓ PropertyAvailability ↓ Calendar Engine ↓ Inventory Engine ↓ Reservation Engine
model PropertyAvailability {
id String
@id
@default(uuid())
tenantId String
propertyId String
startDate DateTime
endDate DateTime
status AvailabilityStatus
inventory Int
@default(1)
availableUnits Int
@default(1)
reason String?
notes String?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
@@index([propertyId])
@@index([startDate])
@@index([endDate])
@@index([status])
}
enum AvailabilityStatus {
AVAILABLE
BLOCKED
RESERVED
MAINTENANCE
CLOSED
}
availabilities PropertyAvailability[]
npx prisma migrate dev \
--name property_availability
npx prisma generate
src/modules/property-availability ├── application │ │ └── dto │ │ ├── create-property-availability.dto.ts │ │ ├── update-property-availability.dto.ts │ │ ├── availability-query.dto.ts │ │ └── calendar-response.dto.ts │ ├── domain │ │ └── services │ │ └── property-availability.service.ts │ ├── presentation │ │ └── controllers │ │ └── property-availability.controller.ts │ └── property-availability.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
AuditModule
],
controllers: [
PropertyAvailabilityController
],
providers: [
PropertyAvailabilityService
],
exports: [
PropertyAvailabilityService
]
})
export class PropertyAvailabilityModule {}
export class CreatePropertyAvailabilityDto {
startDate: Date;
endDate: Date;
status: AvailabilityStatus;
inventory?: number;
availableUnits?: number;
reason?: string;
notes?: string;
}
export class UpdatePropertyAvailabilityDto
extends PartialType(
CreatePropertyAvailabilityDto
) {}
export class AvailabilityQueryDto {
startDate?: Date;
endDate?: Date;
status?: AvailabilityStatus;
}
property-availability.service.ts
findAll() findCalendar() create() update() remove() checkAvailability()
async create(
tenantId: string,
propertyId: string,
dto: CreatePropertyAvailabilityDto,
) {
return this.prisma.propertyAvailability.create({
data: {
tenantId,
propertyId,
...dto
}
});
}
startDate < endDate
et absence de conflits.
Périodes superposées
pour les statuts incompatibles.
where: {
propertyId,
startDate: {
lte: dto.endDate
},
endDate: {
gte: dto.startDate
}
}
GET /properties/{id}/availability
[
{
"startDate":"2026-07-01",
"endDate":"2026-07-10",
"status":"AVAILABLE"
}
]
GET /properties/{id}/availability/calendar
month year
Maintenance Travaux Usage privé Fermeture saisonnière
{
"status":"MAINTENANCE",
"reason":"Pool renovation"
}
POST /properties/{id}/availability/block
inventory availableUnits
Inventaire : 10 Disponibles : 7 Réservées : 3
availableUnits > inventory
property-availability.controller.ts
@ApiTags( 'Property Availability' ) @ApiBearerAuth() @Controller( 'properties/:id/availability' ) @UseGuards( JwtAuthGuard )
GET /properties/{id}/availability
GET /properties/{id}/availability/calendar
POST /properties/{id}/availability
PUT /properties/{id}/availability/{availabilityId}
DELETE /properties/{id}/availability/{availabilityId}
POST /properties/{id}/availability/block
tenantId propertyId
sur toutes les opérations.
property.availability.read property.availability.create property.availability.update property.availability.delete property.availability.manage
PROPERTY_AVAILABILITY_CREATED PROPERTY_AVAILABILITY_UPDATED PROPERTY_AVAILABILITY_DELETED PROPERTY_BLOCKED PROPERTY_UNBLOCKED
Préparer :
Reservation Booking Engine Inventory Engine Channel Manager
Compatible avec :
iCal Airbnb Booking.com Expedia
Compatible avec :
Dynamic Pricing Yield Management Revenue Engine
Le Sprint 4-E.1 est terminé lorsque :
✓ PropertyAvailability créé ✓ PropertyAvailabilityModule créé ✓ PropertyAvailabilityController créé ✓ PropertyAvailabilityService créé ✓ Calendrier opérationnel ✓ Blocages opérationnels ✓ Inventaire opérationnel ✓ Multi-tenant Security ✓ AuditLog intégré
PropertyAvailability AvailabilityStatus PropertyAvailabilityModule PropertyAvailabilityController PropertyAvailabilityService Availability Calendar API
Transformer le moteur de disponibilités en véritable plateforme Channel Manager Enterprise.
À l'issue de cette étape :
✓ iCal ✓ Airbnb Sync ✓ Booking.com Sync ✓ Expedia Sync ✓ Channel Manager ✓ Availability Intelligence ✓ Conflict Resolution ✓ Multi-Channel Inventory
Property Availability ↓ Channel Manager ├── iCal ├── Airbnb ├── Booking.com ├── Expedia ├── Direct Booking └── Future OTA Connectors ↓ Availability Intelligence ↓ Reservation Engine
model ChannelConnection {
id String
@id
@default(uuid())
tenantId String
propertyId String
channelType ChannelType
externalPropertyId String?
syncEnabled Boolean
@default(true)
lastSyncAt DateTime?
syncStatus SyncStatus
@default(PENDING)
configuration Json?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
@@index([propertyId])
@@index([channelType])
}
model AvailabilitySyncLog {
id String
@id
@default(uuid())
channelConnectionId String
syncType SyncType
status SyncStatus
startedAt DateTime
completedAt DateTime?
recordsProcessed Int
@default(0)
errorMessage String?
payload Json?
@@index([status])
@@index([startedAt])
}
model AvailabilityConflict {
id String
@id
@default(uuid())
propertyId String
channelType ChannelType
conflictType ConflictType
localData Json
externalData Json
resolved Boolean
@default(false)
resolvedAt DateTime?
resolvedBy String?
createdAt DateTime
@default(now())
@@index([propertyId])
@@index([resolved])
}
enum ChannelType {
ICAL
AIRBNB
BOOKING
EXPEDIA
DIRECT
}
enum SyncStatus {
PENDING
RUNNING
SUCCESS
FAILED
PARTIAL
}
enum SyncType {
IMPORT
EXPORT
BIDIRECTIONAL
}
enum ConflictType {
INVENTORY
BOOKING
AVAILABILITY
PRICE
}
npx prisma migrate dev \
--name channel_manager
src/modules/channel-manager ├── connectors │ │ ├── ical │ │ ├── airbnb │ │ ├── booking │ │ └── expedia │ ├── synchronization │ ├── conflict-resolution │ ├── intelligence │ └── channel-manager.module.ts
ChannelManagerService AvailabilitySyncService ConflictResolutionService AvailabilityIntelligenceService
icalUrl String? icalExportUrl String?
dans :
ChannelConnection
ical-sync.service.ts
importCalendar() exportCalendar() syncCalendar()
POST /channels/ical/connect POST /channels/ical/sync GET /channels/ical/status
airbnb-connector.service.ts
syncAvailability() syncInventory() syncReservations()
Property Availability Inventory Reservations
POST /channels/airbnb/connect POST /channels/airbnb/sync
booking-connector.service.ts
syncAvailability() syncInventory() syncRates()
POST /channels/booking/connect POST /channels/booking/sync
expedia-connector.service.ts
syncAvailability() syncInventory() syncRates()
POST /channels/expedia/connect POST /channels/expedia/sync
conflict-resolution.service.ts
detectConflicts() resolveConflict() autoResolve()
Double réservation Inventaire incohérent Disponibilité divergente Tarif divergent
Reservation Engine ↓ Direct Booking ↓ OTA
availability-intelligence.service.ts
detectGaps() forecastOccupancy() recommendAvailabilityRules()
Dates non ouvertes Blocages anormaux Fenêtres d'opportunité Périodes sous-occupées
Weekend libre Taux occupation élevé ↓ Suggestion ouverture
GET /channels/dashboard
{
"connectedChannels":4,
"lastSync":"2026-07-01T10:00:00Z",
"pendingConflicts":2,
"occupancyForecast":84
}
Canaux actifs Derniers syncs Erreurs Conflits Performance OTA
channel.read channel.manage channel.sync channel.resolve channel.admin
CHANNEL_CONNECTED CHANNEL_SYNC_STARTED CHANNEL_SYNC_COMPLETED CHANNEL_CONFLICT_DETECTED CHANNEL_CONFLICT_RESOLVED
Cron :
Toutes les 15 min iCal Sync OTA Sync Conflict Detection
Préparer :
Reservation Booking Engine Inventory Locking Overbooking Protection
Préparer :
Enterprise Channel Manager Multi OTA Revenue Management Enterprise Inventory
Le Sprint 4-E.2 est terminé lorsque :
✓ iCal Sync ✓ Airbnb Connector ✓ Booking Connector ✓ Expedia Connector ✓ Channel Manager ✓ Conflict Resolution ✓ Availability Intelligence ✓ Dashboard ✓ Audit intégré
ChannelConnection AvailabilitySyncLog AvailabilityConflict ChannelManagerService AvailabilitySyncService ConflictResolutionService AvailabilityIntelligenceService Enterprise Channel Manager
Implémenter le moteur tarifaire des propriétés.
Cette couche constitue la fondation du futur Revenue Management System.
À l'issue de cette étape :
✓ PropertyPricing ✓ PropertyPricingModule ✓ PropertyPricingController ✓ PropertyPricingService ✓ Tarifs de base ✓ Tarifs saisonniers ✓ Tarifs par période ✓ Inventaire tarifaire ✓ Revenue Ready
Property ↓ PropertyPricing ↓ Pricing Engine ↓ Reservation Engine ↓ Revenue Management
model PropertyPricing {
id String
@id
@default(uuid())
tenantId String
propertyId String
pricingType PricingType
name String
startDate DateTime?
endDate DateTime?
basePrice Decimal
@db.Decimal(12,2)
weekendPrice Decimal?
@db.Decimal(12,2)
extraGuestPrice Decimal?
@db.Decimal(12,2)
cleaningFee Decimal?
@db.Decimal(12,2)
serviceFee Decimal?
@db.Decimal(12,2)
securityDeposit Decimal?
@db.Decimal(12,2)
minimumStay Int?
@default(1)
maximumStay Int?
priority Int
@default(0)
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
@@index([propertyId])
@@index([pricingType])
@@index([startDate])
@@index([endDate])
@@index([active])
}
enum PricingType {
BASE
SEASONAL
SPECIAL_EVENT
PROMOTIONAL
LAST_MINUTE
LONG_STAY
}
pricingRules PropertyPricing[]
npx prisma migrate dev \
--name property_pricing
npx prisma generate
src/modules/property-pricing ├── application │ │ └── dto │ │ ├── create-property-pricing.dto.ts │ │ ├── update-property-pricing.dto.ts │ │ ├── pricing-query.dto.ts │ │ └── pricing-calculation.dto.ts │ ├── domain │ │ └── services │ │ ├── property-pricing.service.ts │ │ └── pricing-calculation.service.ts │ ├── presentation │ │ └── controllers │ │ └── property-pricing.controller.ts │ └── property-pricing.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
AuditModule
],
controllers: [
PropertyPricingController
],
providers: [
PropertyPricingService,
PricingCalculationService
],
exports: [
PropertyPricingService,
PricingCalculationService
]
})
export class PropertyPricingModule {}
export class CreatePropertyPricingDto {
pricingType: PricingType;
name: string;
startDate?: Date;
endDate?: Date;
basePrice: number;
weekendPrice?: number;
extraGuestPrice?: number;
cleaningFee?: number;
serviceFee?: number;
securityDeposit?: number;
minimumStay?: number;
maximumStay?: number;
priority?: number;
}
export class UpdatePropertyPricingDto
extends PartialType(
CreatePropertyPricingDto
) {}
export class PricingQueryDto {
startDate?: Date;
endDate?: Date;
pricingType?: PricingType;
active?: boolean;
}
property-pricing.service.ts
findAll() findById() create() update() remove() findApplicablePricing()
basePrice > 0 minimumStay >= 1 startDate <= endDate
Chevauchements de périodes.
LAST_MINUTE ↓ PROMOTIONAL ↓ SPECIAL_EVENT ↓ SEASONAL ↓ BASE
Règle active :
plus haute priorité
pour une période donnée.
Une règle :
PricingType.BASE
par propriété.
{
"pricingType":"BASE",
"name":"Tarif Standard",
"basePrice":120
}
Haute saison Basse saison Vacances Événements
{
"pricingType":"SEASONAL",
"name":"Été 2027",
"startDate":"2027-06-01",
"endDate":"2027-09-01",
"basePrice":180
}
Compatible avec :
PropertyAvailability
pricing-calculation.service.ts
calculatePrice() calculateStayPrice() calculateFees() calculateDeposit()
3 nuits Base : 120 € ↓ 360 €
Cleaning Fee Service Fee Security Deposit Extra Guests
{
"baseAmount":360,
"cleaningFee":30,
"serviceFee":15,
"total":405
}
property-pricing.controller.ts
@ApiTags( 'Property Pricing' ) @ApiBearerAuth() @Controller( 'properties/:id/pricing' ) @UseGuards( JwtAuthGuard )
GET /properties/{id}/pricing
POST /properties/{id}/pricing
PUT /properties/{id}/pricing/{pricingId}
DELETE /properties/{id}/pricing/{pricingId}
POST /properties/{id}/pricing/calculate
{
"checkIn":"2027-07-10",
"checkOut":"2027-07-15",
"guests":4
}
tenantId propertyId
sur toutes les opérations.
property.pricing.read property.pricing.create property.pricing.update property.pricing.delete property.pricing.calculate
PROPERTY_PRICING_CREATED PROPERTY_PRICING_UPDATED PROPERTY_PRICING_DELETED PROPERTY_PRICE_CALCULATED
Préparer :
Dynamic Pricing Yield Management Revenue Forecast Competitor Rates
Préparer :
Reservation Booking Engine Channel Manager Revenue Management
Le Sprint 4-F.1 est terminé lorsque :
✓ PropertyPricing créé ✓ PropertyPricingModule créé ✓ PropertyPricingController créé ✓ PropertyPricingService créé ✓ Tarifs de base ✓ Tarifs saisonniers ✓ Tarifs par période ✓ Calcul tarifaire ✓ Audit intégré
PropertyPricing PricingType PropertyPricingModule PropertyPricingController PropertyPricingService PricingCalculationService Property Pricing API
Transformer le moteur tarifaire en véritable Revenue Management System Enterprise.
À l'issue de cette étape :
✓ Dynamic Pricing ✓ Yield Management ✓ Occupancy Pricing ✓ Competitor Pricing ✓ Demand Forecast ✓ Revenue Intelligence ✓ Revenue Optimization Engine
Property Pricing ↓ Revenue Engine ├── Dynamic Pricing ├── Yield Management ├── Occupancy Analysis ├── Competitor Intelligence ├── Demand Forecast └── Revenue Optimization ↓ Reservation Engine ↓ Channel Manager
model DynamicPricingRule {
id String
@id
@default(uuid())
tenantId String
propertyId String
name String
active Boolean
@default(true)
triggerType DynamicPricingTrigger
triggerValue Float
adjustmentType PriceAdjustmentType
adjustmentValue Float
priority Int
@default(0)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
@@index([tenantId])
@@index([propertyId])
@@index([active])
}
model RevenueSnapshot {
id String
@id
@default(uuid())
propertyId String
snapshotDate DateTime
occupancyRate Float
averageDailyRate Decimal
@db.Decimal(12,2)
revPar Decimal
@db.Decimal(12,2)
projectedRevenue Decimal
@db.Decimal(14,2)
createdAt DateTime
@default(now())
@@index([propertyId])
@@index([snapshotDate])
}
model CompetitorRate {
id String
@id
@default(uuid())
propertyId String
competitorName String
competitorPropertyId String?
stayDate DateTime
nightlyRate Decimal
@db.Decimal(12,2)
source String?
collectedAt DateTime
@default(now())
@@index([propertyId])
@@index([stayDate])
}
enum DynamicPricingTrigger {
OCCUPANCY
DEMAND
BOOKING_WINDOW
COMPETITOR_RATE
EVENT
SEASON
}
enum PriceAdjustmentType {
PERCENTAGE
FIXED_AMOUNT
}
npx prisma migrate dev \
--name revenue_management
src/modules/revenue-management ├── dynamic-pricing │ ├── yield-management │ ├── occupancy-pricing │ ├── competitor-pricing │ ├── demand-forecast │ ├── revenue-intelligence │ └── revenue-management.module.ts
DynamicPricingService YieldManagementService OccupancyPricingService CompetitorPricingService DemandForecastService RevenueIntelligenceService
dynamic-pricing.service.ts
calculateDynamicPrice() applyPricingRules() simulatePricing()
Occupation > 85% ↓ +15%
120 € ↓ 138 €
POST /revenue/dynamic-pricing/calculate
yield-management.service.ts
calculateOptimalRate() calculateYield() optimizeRevenue()
Occupation Historique réservations Saisonnalité Demande Concurrence
ADR RevPAR Revenue per Stay Yield Index
occupancy-pricing.service.ts
adjustByOccupancy() predictOccupancyImpact()
Occupation 95% ↓ +20%
Occupation 30% ↓ -15%
0-30% 31-60% 61-85% 86-100%
competitor-pricing.service.ts
collectCompetitorRates() compareRates() recommendAdjustment()
Votre prix 120 € Concurrence 145 €
Augmenter à 132 €
GET /revenue/competitors
demand-forecast.service.ts
forecastDemand() forecastRevenue() forecastOccupancy()
Réservations Historique Événements Saisonnalité Recherche
Juillet Demande +24%
Augmenter tarifs
revenue-intelligence.service.ts
generateInsights() detectRevenueOpportunities() recommendStrategies()
Week-end Taux réservation 96%
+12% tarif
Sous-performance Août ↓ Promotion ciblée
model RevenueInsight {
id String
@id
@default(uuid())
propertyId String
severity String
title String
description String
recommendation String?
createdAt DateTime
@default(now())
resolved Boolean
@default(false)
@@index([propertyId])
@@index([resolved])
}
GET /revenue/dashboard
Revenue ADR RevPAR Occupation Forecast Opportunités
{
"adr":142,
"revPar":121,
"occupancy":85,
"forecastRevenue":42000
}
RevenueSnapshot
quotidiennement.
revenue.read revenue.manage revenue.optimize revenue.forecast revenue.admin
REVENUE_RULE_CREATED DYNAMIC_PRICE_CALCULATED REVENUE_FORECAST_GENERATED REVENUE_INSIGHT_CREATED REVENUE_OPTIMIZATION_APPLIED
Toutes les heures Dynamic Pricing Toutes les nuits Revenue Forecast Revenue Insights
Compatible avec :
Reservation Booking Engine Upsell Promotion Engine
Compatible avec :
Financial Forecast Profitability Analysis Business Intelligence
Compatible avec :
Enterprise Revenue Management AI Pricing Predictive Revenue Revenue Intelligence
Le Sprint 4-F.2 est terminé lorsque :
✓ Dynamic Pricing ✓ Yield Management ✓ Occupancy Pricing ✓ Competitor Pricing ✓ Demand Forecast ✓ Revenue Intelligence ✓ Revenue Dashboard ✓ Audit intégré
DynamicPricingRule RevenueSnapshot CompetitorRate RevenueInsight DynamicPricingService YieldManagementService DemandForecastService RevenueIntelligenceService Revenue Dashboard
Implémenter le moteur de politiques métier des propriétés.
Cette couche constitue la fondation des règles de réservation, d'annulation et d'exploitation.
À l'issue de cette étape :
✓ PropertyPolicy ✓ PropertyPolicyModule ✓ PropertyPolicyController ✓ PropertyPolicyService ✓ Check-in ✓ Check-out ✓ Annulation ✓ Règlement intérieur ✓ Reservation Ready
Property ↓ PropertyPolicy ├── Check-in ├── Check-out ├── Cancellation ├── House Rules ├── Guest Rules └── Booking Restrictions ↓ Reservation Engine ↓ Revenue Engine
model PropertyPolicy {
id String
@id
@default(uuid())
tenantId String
propertyId String
@unique
cancellationPolicy CancellationPolicyType
checkInFrom String?
checkInTo String?
checkOutFrom String?
checkOutTo String?
minimumGuestAge Int?
petsAllowed Boolean
@default(false)
smokingAllowed Boolean
@default(false)
partiesAllowed Boolean
@default(false)
childrenAllowed Boolean
@default(true)
eventsAllowed Boolean
@default(false)
quietHoursStart String?
quietHoursEnd String?
houseRules String?
guestInstructions String?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
@@index([cancellationPolicy])
}
enum CancellationPolicyType {
FLEXIBLE
MODERATE
STRICT
NON_REFUNDABLE
CUSTOM
}
policy PropertyPolicy?
npx prisma migrate dev \
--name property_policy
npx prisma generate
Annulation gratuite jusqu'à 24h avant arrivée
Annulation gratuite jusqu'à 5 jours avant arrivée
Remboursement partiel selon conditions
Aucun remboursement
customCancellationPolicy String?
pour :
CancellationPolicyType.CUSTOM
Check-in From Check-in To Check-out From Check-out To
Check-in 15:00 → 22:00
Check-out 08:00 → 11:00
Format :
HH:mm
sur tous les horaires.
Animaux Fumeurs Fêtes Enfants Événements
{
"petsAllowed":true,
"smokingAllowed":false,
"partiesAllowed":false
}
houseRules guestInstructions
pour les consignes détaillées.
src/modules/property-policy ├── application │ │ └── dto │ │ ├── update-property-policy.dto.ts │ │ └── property-policy-response.dto.ts │ ├── domain │ │ └── services │ │ └── property-policy.service.ts │ ├── presentation │ │ └── controllers │ │ └── property-policy.controller.ts │ └── property-policy.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
AuditModule
],
controllers: [
PropertyPolicyController
],
providers: [
PropertyPolicyService
],
exports: [
PropertyPolicyService
]
})
export class PropertyPolicyModule {}
export class UpdatePropertyPolicyDto {
cancellationPolicy:
CancellationPolicyType;
customCancellationPolicy?: string;
checkInFrom?: string;
checkInTo?: string;
checkOutFrom?: string;
checkOutTo?: string;
minimumGuestAge?: number;
petsAllowed?: boolean;
smokingAllowed?: boolean;
partiesAllowed?: boolean;
childrenAllowed?: boolean;
eventsAllowed?: boolean;
quietHoursStart?: string;
quietHoursEnd?: string;
houseRules?: string;
guestInstructions?: string;
}
property-policy.service.ts
getPolicy() updatePolicy() validatePolicy() initializePolicy()
lors de la création d'une propriété :
MODERATE 15:00-22:00 08:00-11:00
comme valeurs par défaut.
Check-in Check-out Âge minimum Règles cohérentes
avant sauvegarde.
property-policy.controller.ts
@ApiTags( 'Property Policies' ) @ApiBearerAuth() @Controller( 'properties/:id/policies' ) @UseGuards( JwtAuthGuard )
GET /properties/{id}/policies
PUT /properties/{id}/policies
{
"cancellationPolicy":"MODERATE",
"checkInFrom":"15:00",
"checkInTo":"22:00",
"petsAllowed":true
}
tenantId propertyId
sur toutes les opérations.
property.policy.read property.policy.update property.policy.manage
PROPERTY_POLICY_VIEWED PROPERTY_POLICY_UPDATED PROPERTY_CANCELLATION_UPDATED PROPERTY_RULES_UPDATED
Préparer :
Reservation Booking Workflow Refund Engine Guest Management
Préparer :
Revenue Management Legal Compliance Terms & Conditions Insurance Rules
Compatible avec :
Airbnb Booking.com Expedia OTA Mapping
Le Sprint 4-G.1 est terminé lorsque :
✓ PropertyPolicy créé ✓ PropertyPolicyModule créé ✓ PropertyPolicyController créé ✓ PropertyPolicyService créé ✓ Check-in géré ✓ Check-out géré ✓ Annulation gérée ✓ Règlement intérieur géré ✓ Audit intégré
PropertyPolicy CancellationPolicyType PropertyPolicyModule PropertyPolicyController PropertyPolicyService Property Policy API
Transformer le moteur de politiques en véritable couche de gouvernance juridique Enterprise.
À l'issue de cette étape :
✓ Conditions contractuelles ✓ Dépôts de garantie ✓ Conformité locale ✓ RGPD ✓ Documents légaux ✓ Signature électronique ✓ Gestion des exceptions ✓ Legal Compliance Engine
Property Policy ↓ Legal Engine ├── Contracts ├── Deposits ├── Compliance ├── GDPR ├── Legal Documents ├── E-Signature └── Exceptions ↓ Reservation Engine ↓ Governance Layer
model PropertyLegalPolicy {
id String
@id
@default(uuid())
tenantId String
propertyId String
@unique
termsAndConditions String?
privacyPolicy String?
liabilityWaiver String?
cancellationTerms String?
localComplianceNotes String?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
}
model SecurityDepositPolicy {
id String
@id
@default(uuid())
propertyId String
@unique
depositRequired Boolean
@default(false)
depositAmount Decimal?
@db.Decimal(12,2)
depositCurrency String?
@default("EUR")
refundDelayDays Int?
@default(7)
refundConditions String?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
@@index([propertyId])
}
model LegalDocument {
id String
@id
@default(uuid())
tenantId String
propertyId String?
documentType LegalDocumentType
version String
title String
storagePath String
published Boolean
@default(false)
effectiveDate DateTime
createdAt DateTime
@default(now())
@@index([tenantId])
@@index([documentType])
@@index([effectiveDate])
}
enum LegalDocumentType {
TERMS
PRIVACY_POLICY
HOUSE_RULES
RENTAL_CONTRACT
LIABILITY_WAIVER
GDPR_NOTICE
}
npx prisma migrate dev \
--name legal_compliance
src/modules/legal ├── contracts │ ├── compliance │ ├── gdpr │ ├── signatures │ └── documents
contract.service.ts
generateContract() validateContract() publishContract() archiveContract()
à partir de :
Property Reservation Policies Guest
security-deposit.service.ts
calculateDeposit() reserveDeposit() releaseDeposit() claimDeposit()
Montant fixe Pourcentage réservation Selon catégorie
Réservation 1 000 € Dépôt 20 % ↓ 200 €
model LocalComplianceRule {
id String
@id
@default(uuid())
countryCode String
regionCode String?
ruleType String
title String
description String
active Boolean
@default(true)
createdAt DateTime
@default(now())
@@index([countryCode])
@@index([ruleType])
}
compliance.service.ts
validatePropertyCompliance() getApplicableRules() generateComplianceReport()
Taxe séjour Déclaration hébergement Assurance Capacité légale Sécurité incendie
model GDPRConsent {
id String
@id
@default(uuid())
userId String
consentType String
accepted Boolean
acceptedAt DateTime?
revokedAt DateTime?
ipAddress String?
createdAt DateTime
@default(now())
@@index([userId])
@@index([consentType])
}
gdpr.service.ts
recordConsent() revokeConsent() exportUserData() anonymizeUserData()
Consentement Portabilité Rectification Droit à l'oubli
model ElectronicSignature {
id String
@id
@default(uuid())
documentId String
signerId String
signedAt DateTime?
ipAddress String?
status SignatureStatus
signatureHash String?
@@index([documentId])
@@index([signerId])
}
enum SignatureStatus {
PENDING
SIGNED
REJECTED
EXPIRED
}
signature.service.ts
requestSignature() signDocument() verifySignature()
model PolicyException {
id String
@id
@default(uuid())
propertyId String
exceptionType String
reason String
approvedBy String?
validFrom DateTime?
validUntil DateTime?
active Boolean
@default(true)
createdAt DateTime
@default(now())
@@index([propertyId])
@@index([active])
}
Animal exceptionnel Check-in tardif Annulation exceptionnelle Remise exceptionnelle
Approbation selon RBAC.
GET /properties/{id}/legal
PUT /properties/{id}/legal
GET /properties/{id}/compliance
POST /properties/{id}/compliance/validate
POST /contracts/generate
POST /contracts/sign
GET /gdpr/export
POST /gdpr/delete-request
GET /legal-documents
POST /legal-documents
PUT /legal-documents/{id}
DELETE /legal-documents/{id}
legal.read legal.manage legal.contracts legal.compliance legal.gdpr legal.signatures
LEGAL_DOCUMENT_CREATED CONTRACT_GENERATED CONTRACT_SIGNED GDPR_EXPORT_REQUESTED GDPR_DELETE_REQUESTED COMPLIANCE_VALIDATED POLICY_EXCEPTION_CREATED
Compliance Score GDPR Status Pending Signatures Legal Risks
Préparer :
Reservation Contract Guest Agreements Refund Workflow
Préparer :
Governance Risk Management Audit Framework Compliance Dashboard
Préparer :
Enterprise Compliance Legal Automation Contract Intelligence Global Regulations
Le Sprint 4-G.2 est terminé lorsque :
✓ Contrats gérés ✓ Dépôts de garantie gérés ✓ Conformité locale gérée ✓ RGPD géré ✓ Documents légaux gérés ✓ Signature électronique gérée ✓ Exceptions gérées ✓ Audit intégré
PropertyLegalPolicy SecurityDepositPolicy LegalDocument ElectronicSignature GDPRConsent PolicyException ComplianceService GDPRService SignatureService Enterprise Legal Engine
Implémenter le système d'avis clients des propriétés.
Cette couche constitue la fondation de la réputation, de la confiance et du classement des biens.
À l'issue de cette étape :
✓ PropertyReview ✓ PropertyReviewModule ✓ PropertyReviewController ✓ PropertyReviewService ✓ Avis ✓ Notes ✓ Commentaires ✓ Réponses propriétaires ✓ Reputation Ready
Reservation ↓ Review Eligibility ↓ PropertyReview ├── Rating ├── Comment ├── Owner Response ├── Moderation └── Analytics ↓ Property Score ↓ Search Ranking
model PropertyReview {
id String
@id
@default(uuid())
tenantId String
propertyId String
customerId String
reservationId String?
overallRating Int
cleanlinessRating Int?
communicationRating Int?
locationRating Int?
valueRating Int?
checkInRating Int?
title String?
comment String?
verifiedStay Boolean
@default(false)
published Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
customer Customer
@relation(
fields:[customerId],
references:[id]
)
responses PropertyReviewResponse[]
@@index([tenantId])
@@index([propertyId])
@@index([customerId])
@@index([overallRating])
@@index([published])
}
model PropertyReviewResponse {
id String
@id
@default(uuid())
reviewId String
userId String
response String
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
review PropertyReview
@relation(
fields:[reviewId],
references:[id],
onDelete:Cascade
)
@@index([reviewId])
}
reviews PropertyReview[]
npx prisma migrate dev \
--name property_reviews
npx prisma generate
src/modules/property-reviews ├── application │ │ └── dto │ │ ├── create-review.dto.ts │ │ ├── update-review.dto.ts │ │ ├── review-response.dto.ts │ │ └── review-query.dto.ts │ ├── domain │ │ └── services │ │ └── property-review.service.ts │ ├── presentation │ │ └── controllers │ │ └── property-review.controller.ts │ └── property-review.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
AuditModule
],
controllers: [
PropertyReviewController
],
providers: [
PropertyReviewService
],
exports: [
PropertyReviewService
]
})
export class PropertyReviewModule {}
export class CreateReviewDto {
reservationId?: string;
overallRating: number;
cleanlinessRating?: number;
communicationRating?: number;
locationRating?: number;
valueRating?: number;
checkInRating?: number;
title?: string;
comment?: string;
}
1 <= Rating <= 5
sur toutes les notes.
export class ReviewResponseDto {
response: string;
}
property-review.service.ts
findAll() findById() create() update() remove() respond() calculatePropertyRating()
Le client doit :
Posséder réservation ET Réservation terminée
pour déposer un avis.
verifiedStay = true
si réservation validée.
plusieurs avis :
reservationId
identique.
Global Propreté Communication Emplacement Rapport qualité/prix Check-in
AVG(overallRating)
par propriété.
PropertyScore.reviewScore
automatiquement.
POST /reviews/{id}/response
Property Manager Tenant Admin Owner
uniquement.
{
"response":"Merci pour votre séjour."
}
property-review.controller.ts
@ApiTags( 'Property Reviews' ) @ApiBearerAuth()
GET /properties/{id}/reviews
POST /properties/{id}/reviews
PUT /reviews/{id}
DELETE /reviews/{id}
POST /reviews/{id}/response
Rating Verified Date Published
model ReviewModeration {
id String
@id
@default(uuid())
reviewId String
@unique
flagged Boolean
@default(false)
reason String?
reviewedBy String?
reviewedAt DateTime?
createdAt DateTime
@default(now())
}
Spam Abus Langage interdit Fraude
Publish Hide Delete Escalate
tenantId propertyId
sur toutes les opérations.
property.review.read property.review.create property.review.update property.review.delete property.review.respond property.review.moderate
PROPERTY_REVIEW_CREATED PROPERTY_REVIEW_UPDATED PROPERTY_REVIEW_DELETED PROPERTY_REVIEW_RESPONDED PROPERTY_REVIEW_MODERATED
Préparer :
Review Images Review Sentiment AI Moderation Review Analytics
Préparer :
Search Ranking Property Score Recommendation Engine Trust Score
Le Sprint 4-H.1 est terminé lorsque :
✓ PropertyReview créé ✓ PropertyReviewModule créé ✓ PropertyReviewController créé ✓ PropertyReviewService créé ✓ Avis opérationnels ✓ Notes opérationnelles ✓ Commentaires opérationnels ✓ Réponses propriétaires opérationnelles ✓ Modération intégrée
PropertyReview PropertyReviewResponse ReviewModeration PropertyReviewModule PropertyReviewController PropertyReviewService Property Reviews API
Transformer le système d'avis en plateforme de réputation intelligente Enterprise.
À l'issue de cette étape :
✓ Analyse de sentiment ✓ IA de modération ✓ Review Images ✓ Détection fraude ✓ Trust Score ✓ Réputation globale ✓ Insights clients ✓ Reputation Intelligence Platform
Property Reviews ↓ Review Intelligence Engine ├── Sentiment Analysis ├── AI Moderation ├── Review Images ├── Fraud Detection ├── Trust Scoring ├── Reputation Analytics └── Customer Insights ↓ Property Score ↓ Search Ranking ↓ Recommendation Engine
model ReviewSentiment {
id String
@id
@default(uuid())
reviewId String
@unique
sentiment SentimentType
score Float
positiveKeywords Json?
negativeKeywords Json?
emotions Json?
analyzedAt DateTime
@default(now())
review PropertyReview
@relation(
fields:[reviewId],
references:[id],
onDelete:Cascade
)
}
model ReviewTrustScore {
id String
@id
@default(uuid())
reviewId String
@unique
trustScore Float
fraudRisk Float
authenticityScore Float
behaviorScore Float
calculatedAt DateTime
@default(now())
review PropertyReview
@relation(
fields:[reviewId],
references:[id],
onDelete:Cascade
)
}
model ReviewImage {
id String
@id
@default(uuid())
reviewId String
imageUrl String
thumbnailUrl String?
moderationStatus ModerationStatus
@default(PENDING)
uploadedAt DateTime
@default(now())
review PropertyReview
@relation(
fields:[reviewId],
references:[id],
onDelete:Cascade
)
@@index([reviewId])
}
enum SentimentType {
VERY_POSITIVE
POSITIVE
NEUTRAL
NEGATIVE
VERY_NEGATIVE
}
enum ModerationStatus {
PENDING
APPROVED
REJECTED
FLAGGED
}
npx prisma migrate dev \
--name review_intelligence
review-sentiment.service.ts
analyzeSentiment() extractKeywords() detectEmotions() generateSummary()
Très positif Positif Neutre Négatif Très négatif
Appartement magnifique, très propre, hôte exceptionnel
Sentiment: VERY_POSITIVE Score: 94
review-moderation-ai.service.ts
detectSpam() detectAbuse() detectToxicity() detectFakeReview()
Spam Insultes Contenu haineux Fraude Bots
Approve Flag Reject Escalate
POST /reviews/{id}/images
Format Poids Qualité Contenu interdit
Chambre Salle de bain Piscine Cuisine Animaux Personnes
aux équipements détectés.
review-fraud-detection.service.ts
detectFraud() calculateRisk() detectReviewFarm() detectDuplicatePatterns()
IP Device Timing Texte Historique
Suspicious Review Fake Review Mass Review Activity
review-trust-score.service.ts
Verified Stay Historique client Fraud Risk Sentiment Consistency Review Completeness
90-100 Trusted 75-89 Reliable 50-74 Medium 0-49 Suspicious
Trust Score 96 Trusted
reputation.service.ts
calculatePropertyReputation() calculateTenantReputation() calculateBrandReputation()
Average Rating Review Volume Trust Score Response Rate Response Time
Property Reputation 92 Excellent
model ReviewInsight {
id String
@id
@default(uuid())
propertyId String
category String
title String
description String
severity String?
createdAt DateTime
@default(now())
resolved Boolean
@default(false)
@@index([propertyId])
}
Forces Faiblesses Opportunités Problèmes récurrents
32% des avis mentionnent Wifi lent
Améliorer réseau
GET /reviews/reputation/dashboard
{
"averageRating":4.7,
"trustScore":91,
"responseRate":96,
"sentiment":"POSITIVE"
}
Sentiment Trends Review Trends Trust Trends Customer Insights
property.review.analytics property.review.moderate property.review.trust property.review.reputation
REVIEW_ANALYZED REVIEW_FLAGGED REVIEW_FRAUD_DETECTED REPUTATION_UPDATED TRUST_SCORE_UPDATED
Toutes les heures Sentiment Analysis Fraud Detection Toutes les nuits Reputation Calculation Insights Generation
Compatible avec :
Search Ranking Property Score Recommendation Engine
Compatible avec :
LLM Analytics AI Insights Customer Intelligence Knowledge Graph
Compatible avec :
Enterprise Reputation Platform AI Moderation Fraud Prevention Brand Intelligence
Le Sprint 4-H.2 est terminé lorsque :
✓ Analyse de sentiment ✓ IA de modération ✓ Review Images ✓ Détection fraude ✓ Trust Score ✓ Réputation globale ✓ Insights clients ✓ Dashboard réputation ✓ Audit intégré
ReviewSentiment ReviewTrustScore ReviewImage ReviewInsight ReviewSentimentService ReviewModerationAIService FraudDetectionService ReputationService Enterprise Reputation Platform
Construire le moteur de recherche central de la plateforme.
Cette couche unifie :
Property Amenities Availability Pricing Reviews Scores Analytics
dans une expérience de recherche Enterprise unique.
À l'issue de cette étape :
✓ PropertySearchEngine ✓ Faceted Search ✓ Geo Search ✓ Availability Search ✓ Ranking Engine ✓ Search Analytics ✓ Search API Enterprise
Property Search Engine ├── Catalog Search ├── Availability Search ├── Geo Search ├── Pricing Search ├── Review Search ├── Faceted Search ├── Ranking Engine └── Search Analytics ↓ Booking Engine ↓ Recommendation Engine
model SearchQueryLog {
id String
@id
@default(uuid())
tenantId String?
userId String?
query String?
filters Json?
resultCount Int
@default(0)
executionTimeMs Int?
createdAt DateTime
@default(now())
@@index([tenantId])
@@index([userId])
@@index([createdAt])
}
model PropertySearchIndex {
propertyId String
@id
searchableText String
amenities Json?
city String?
country String?
averageRating Float?
reviewCount Int?
minPrice Decimal?
@db.Decimal(12,2)
maxGuests Int?
searchScore Float?
@default(0)
updatedAt DateTime
@updatedAt
@@index([city])
@@index([country])
@@index([searchScore])
}
model SavedPropertySearch {
id String
@id
@default(uuid())
userId String
tenantId String
name String
filters Json
createdAt DateTime
@default(now())
@@index([userId])
@@index([tenantId])
}
npx prisma migrate dev \
--name property_search_engine
src/modules/property-search ├── search │ ├── ranking │ ├── analytics │ ├── facets │ ├── geo │ └── property-search.module.ts
PropertySearchEngineService FacetedSearchService GeoSearchService AvailabilitySearchService RankingEngineService SearchAnalyticsService
export class PropertySearchDto {
query?: string;
city?: string;
country?: string;
latitude?: number;
longitude?: number;
radiusKm?: number;
checkIn?: Date;
checkOut?: Date;
guests?: number;
minPrice?: number;
maxPrice?: number;
minRating?: number;
amenities?: string[];
propertyTypeId?: string;
page?: number = 1;
limit?: number = 25;
}
search()
Property Availability Pricing Reviews Amenities
dans une requête unique.
Name Description Location Amenities Reviews
faceted-search.service.ts
Cities Countries Amenities Ratings Price Ranges Property Types
GET /search/facets
{
"cities": {
"Paris": 132
},
"amenities": {
"Wifi": 218
}
}
geo-search.service.ts
searchByRadius() calculateDistance() sortByDistance()
Latitude Longitude Haversine
Rayon 10 km autour de Paris
availability-search.service.ts
Check-In Check-Out Inventory Blocks
GET /search/availability
Uniquement les biens :
Disponibles
sur la période demandée.
ranking-engine.service.ts
Review Score Trust Score Price Score Availability Score Popularity Revenue Score
Reviews 30% Popularity 20% Availability 20% Price 15% Revenue 15%
PropertySearchIndex.searchScore
automatiquement.
search-analytics.service.ts
trackSearch() getPopularSearches() getZeroResultsQueries() getConversionRate()
Search Volume CTR Bookings Search Conversion Top Filters
GET /search/analytics
GET /search
GET /search/facets
GET /search/availability
GET /search/analytics
POST /search/saved
GET /search/saved
DELETE /search/saved/{id}
Paris 4 personnes Wifi Piscine 14-20 juillet
{
"items": [],
"facets": {},
"meta": {}
}
Offset Cursor Infinite Scroll
property.search property.search.saved property.search.analytics property.search.admin
PROPERTY_SEARCH_EXECUTED PROPERTY_SEARCH_SAVED PROPERTY_SEARCH_ANALYTICS_VIEWED
Toutes les heures Search Index Refresh Toutes les nuits Ranking Rebuild Analytics Aggregation
Compatible avec :
Semantic Search AI Search Recommendations Personalization
Compatible avec :
Vector Search LLM Search Knowledge Graph RAG
Compatible avec :
Enterprise Search Platform Global Catalog AI Discovery Unified Search
Le Sprint 4-I.1 est terminé lorsque :
✓ PropertySearchEngine ✓ Faceted Search ✓ Geo Search ✓ Availability Search ✓ Ranking Engine ✓ Search Analytics ✓ Search API Enterprise ✓ Audit intégré
PropertySearchEngineService FacetedSearchService GeoSearchService AvailabilitySearchService RankingEngineService SearchAnalyticsService PropertySearchIndex Enterprise Search Platform
Transformer le moteur de recherche immobilier en plateforme de découverte intelligente alimentée par l'IA.
À l'issue de cette étape :
✓ Recherche sémantique ✓ Vector Search ✓ Recommandations ✓ Personnalisation ✓ Intent Detection ✓ AI Discovery ✓ Property Intelligence Graph
User Query ↓ AI Discovery Platform ├── Intent Detection ├── Semantic Search ├── Vector Search ├── Recommendation Engine ├── Personalization Engine ├── Property Intelligence Graph └── Ranking AI ↓ Search Engine ↓ Booking Engine
model PropertyEmbedding {
propertyId String
@id
embeddingModel String
embeddingVersion String
vectorId String
generatedAt DateTime
@default(now())
property Property
@relation(
fields:[propertyId],
references:[id],
onDelete:Cascade
)
}
model SearchIntent {
id String
@id
@default(uuid())
query String
detectedIntent String
confidence Float
entities Json?
createdAt DateTime
@default(now())
@@index([detectedIntent])
}
model RecommendationProfile {
id String
@id
@default(uuid())
userId String
@unique
preferences Json?
interests Json?
travelPatterns Json?
preferredAmenities Json?
preferredLocations Json?
updatedAt DateTime
@updatedAt
@@index([userId])
}
model PropertyRecommendation {
id String
@id
@default(uuid())
userId String
propertyId String
recommendationScore Float
reason String?
generatedAt DateTime
@default(now())
clicked Boolean
@default(false)
booked Boolean
@default(false)
@@index([userId])
@@index([propertyId])
}
npx prisma migrate dev \
--name ai_discovery_platform
src/modules/ai-discovery ├── semantic-search │ ├── vector-search │ ├── recommendations │ ├── personalization │ ├── intent-detection │ ├── intelligence-graph │ └── ai-discovery.module.ts
SemanticSearchService VectorSearchService RecommendationEngineService PersonalizationService IntentDetectionService PropertyIntelligenceGraphService
semantic-search.service.ts
semanticSearch() expandQuery() extractConcepts() rankSemanticResults()
Appartement calme pour télétravail près de la mer
Remote Work Wifi Workspace Sea View Quiet Area
Property Amenities Reviews Descriptions Policies
vector-search.service.ts
generateEmbedding() searchSimilarProperties() findNearestNeighbors()
pgvector Qdrant Weaviate Pinecone
Properties Reviews Amenities Policies Locations
Villa luxe avec piscine ↓ Biens similaires
recommendation-engine.service.ts
recommendForUser() recommendSimilarProperties() recommendTrending()
Historique recherches Réservations Favoris Reviews Préférences
Similar Trending Recently Viewed Personalized Popular
personalization.service.ts
buildUserProfile() updatePreferences() personalizeRanking()
Search History Bookings Preferences Behavior
Utilisateur Voyage famille ↓ Prioriser Family Friendly
intent-detection.service.ts
Business Travel Family Vacation Luxury Stay Remote Work Romantic Getaway
Budget Guests Location Amenities Dates
Je cherche un logement calme pour travailler à distance
Intent: REMOTE_WORK Confidence: 94%
property-intelligence-graph.service.ts
Properties Amenities Reviews Locations Customers
Property ↓ Wifi ↓ Remote Work ↓ Business Traveler
Recommendations Semantic Search Discovery
POST /discovery/search
GET /discovery/recommendations
GET /discovery/trending
GET /discovery/similar/{propertyId}
POST /discovery/intent
{
"query":"Villa avec piscine pour famille"
}
{
"intent":"FAMILY_VACATION",
"results":[],
"recommendations":[]
}
Recommendation CTR Search Satisfaction Discovery Conversion Intent Accuracy
discovery.search discovery.recommend discovery.personalization discovery.analytics discovery.admin
SEMANTIC_SEARCH_EXECUTED VECTOR_SEARCH_EXECUTED RECOMMENDATION_GENERATED INTENT_DETECTED DISCOVERY_RESULT_CLICKED
Toutes les heures Recommendation Refresh Toutes les nuits Embedding Generation Intent Training Ranking Rebuild
Compatible avec :
Reservation Suggestions Upsell Engine Cross Sell Engine
Compatible avec :
LLM Assistant RAG Knowledge Graph Agentic Search
Compatible avec :
Enterprise AI Discovery Predictive Booking Hyper Personalization Property Intelligence Platform
Le Sprint 4-I.2 est terminé lorsque :
✓ Recherche sémantique ✓ Vector Search ✓ Recommandations ✓ Personnalisation ✓ Intent Detection ✓ AI Discovery ✓ Analytics IA ✓ Audit intégré
PropertyEmbedding SearchIntent RecommendationProfile PropertyRecommendation SemanticSearchService VectorSearchService RecommendationEngineService PersonalizationService IntentDetectionService PropertyIntelligenceGraphService AI Discovery Platform
Construire le cockpit opérationnel central des propriétés.
Cette couche fournit une vision temps réel de :
Occupation Revenus Avis Disponibilités Performance Réservations
afin de permettre le pilotage métier des propriétés.
À l'issue de cette étape :
✓ Property Dashboard ✓ Occupancy Analytics ✓ Revenue Analytics ✓ Review Analytics ✓ Availability Analytics ✓ Performance KPIs ✓ Executive Dashboard
Property Analytics Platform ├── Occupancy Analytics ├── Revenue Analytics ├── Review Analytics ├── Availability Analytics ├── Search Analytics ├── Booking Analytics └── KPI Dashboard ↓ Property Intelligence ↓ Revenue Management
model PropertyAnalyticsSnapshot {
id String
@id
@default(uuid())
tenantId String
propertyId String
snapshotDate DateTime
occupancyRate Float?
averageDailyRate Decimal?
@db.Decimal(12,2)
revenue Decimal?
@db.Decimal(14,2)
reviewScore Float?
reviewCount Int?
availabilityRate Float?
searchViews Int?
@default(0)
bookings Int?
@default(0)
createdAt DateTime
@default(now())
@@unique([
propertyId,
snapshotDate
])
@@index([tenantId])
@@index([propertyId])
@@index([snapshotDate])
}
model PropertyKPI {
propertyId String
@id
occupancyRate Float?
averageDailyRate Float?
revPar Float?
revenue Float?
reviewScore Float?
conversionRate Float?
searchRanking Float?
reputationScore Float?
updatedAt DateTime
@updatedAt
}
npx prisma migrate dev \
--name property_analytics
src/modules/property-analytics ├── dashboard │ ├── occupancy │ ├── revenue │ ├── reviews │ ├── availability │ ├── kpi │ └── property-analytics.module.ts
PropertyDashboardService OccupancyAnalyticsService RevenueAnalyticsService ReviewAnalyticsService AvailabilityAnalyticsService PropertyKPIService
occupancy-analytics.service.ts
calculateOccupancyRate() calculateOccupancyTrend() forecastOccupancy()
Occupied Nights Available Nights Occupancy % Average Stay
Occupancy 82% ↑ +6%
revenue-analytics.service.ts
calculateRevenue() calculateADR() calculateRevPAR() calculateRevenueTrend()
Revenue ADR RevPAR Revenue Growth
Revenue 45 000 € ↑ 12%
review-analytics.service.ts
calculateAverageRating() analyzeSentimentTrend() calculateResponseRate()
Average Rating Review Count Trust Score Sentiment Trend
Rating 4.8 / 5 ↑ 0.2
availability-analytics.service.ts
calculateAvailabilityRate() detectAvailabilityGaps() analyzeBlockedDates()
Available Days Blocked Days Maintenance Days Availability %
Availability 74% Blocked 12 jours
property-dashboard.service.ts
Occupancy Revenue Reviews Availability Search Bookings
GET /properties/{id}/dashboard
{
"occupancy":82,
"revenue":45000,
"rating":4.8,
"availability":74,
"bookings":34
}
property-kpi.service.ts
Occupancy Score Revenue Score Review Score Availability Score Search Score Global Score
Revenue 30% Occupancy 25% Reviews 20% Availability 15% Search 10%
Top Properties Most Profitable Best Rated Highest Occupancy
GET /properties/{id}/dashboard
GET /properties/{id}/analytics/occupancy
GET /properties/{id}/analytics/revenue
GET /properties/{id}/analytics/reviews
GET /properties/{id}/analytics/availability
GET /properties/{id}/analytics/kpis
Today 7 Days 30 Days 90 Days 12 Months Custom Range
GET /properties/{id}/analytics/export
Formats :
CSV Excel PDF
property.analytics.read property.analytics.export property.analytics.kpi property.analytics.admin
PROPERTY_DASHBOARD_VIEWED PROPERTY_ANALYTICS_VIEWED PROPERTY_KPI_CALCULATED PROPERTY_ANALYTICS_EXPORTED
Toutes les heures KPI Refresh Toutes les nuits Analytics Snapshot Score Rebuild
Préparer :
Revenue Management Booking Engine Business Intelligence Executive Reporting
Le Sprint 4-J.1 est terminé lorsque :
✓ Dashboard propriété ✓ Occupancy Analytics ✓ Revenue Analytics ✓ Review Analytics ✓ Availability Analytics ✓ KPI Engine ✓ Export Analytics ✓ Audit intégré
PropertyAnalyticsSnapshot PropertyKPI PropertyDashboardService OccupancyAnalyticsService RevenueAnalyticsService ReviewAnalyticsService AvailabilityAnalyticsService PropertyKPIService Property Analytics Dashboard
Transformer le cockpit analytique en plateforme Property Intelligence Enterprise.
À l'issue de cette étape :
✓ Prévision occupation ✓ Prévision revenus ✓ Prévision demande ✓ Détection anomalies ✓ Insights IA ✓ Property Health Score ✓ Property Intelligence Platform
Property Analytics ↓ Property Intelligence Engine ├── Occupancy Forecast ├── Revenue Forecast ├── Demand Forecast ├── Anomaly Detection ├── AI Insights ├── Health Scoring └── Optimization Recommendations ↓ Revenue Management ↓ Executive Intelligence
model PropertyForecast {
id String
@id
@default(uuid())
propertyId String
forecastType ForecastType
forecastDate DateTime
predictedValue Float
confidenceScore Float?
generatedAt DateTime
@default(now())
@@index([propertyId])
@@index([forecastType])
@@index([forecastDate])
}
model PropertyInsight {
id String
@id
@default(uuid())
propertyId String
severity InsightSeverity
category InsightCategory
title String
description String
recommendation String?
impactScore Float?
resolved Boolean
@default(false)
createdAt DateTime
@default(now())
@@index([propertyId])
@@index([resolved])
@@index([category])
}
model PropertyHealthScore {
propertyId String
@id
overallScore Float
occupancyScore Float
revenueScore Float
reviewScore Float
availabilityScore Float
complianceScore Float
reputationScore Float
calculatedAt DateTime
@updatedAt
}
enum ForecastType {
OCCUPANCY
REVENUE
DEMAND
BOOKINGS
}
enum InsightSeverity {
LOW
MEDIUM
HIGH
CRITICAL
}
enum InsightCategory {
OCCUPANCY
REVENUE
REVIEWS
DEMAND
AVAILABILITY
COMPLIANCE
}
npx prisma migrate dev \
--name property_intelligence
src/modules/property-intelligence ├── forecasts │ ├── anomalies │ ├── insights │ ├── health-score │ ├── optimization │ └── property-intelligence.module.ts
OccupancyForecastService RevenueForecastService DemandForecastService AnomalyDetectionService PropertyInsightService PropertyHealthScoreService
occupancy-forecast.service.ts
forecastOccupancy() forecastSeasonality() forecastBookingWindow()
Availability Reservations Seasonality Events Historical Occupancy
Août Occupation prévue 91% Confiance 88%
revenue-forecast.service.ts
forecastRevenue() forecastADR() forecastRevPAR()
Revenue History Pricing Occupancy Forecast Demand Forecast
Revenu prévisionnel 52 000 € +14%
demand-forecast.service.ts
Haute saison Vacances Événements Tendances marché
forecastDemand() detectDemandPeaks() recommendPricingActions()
Demande +27% ↓ Augmentation tarifaire recommandée
anomaly-detection.service.ts
detectOccupancyAnomalies() detectRevenueAnomalies() detectReviewAnomalies() detectAvailabilityAnomalies()
Baisse brutale occupation Chute revenus Explosion annulations Avis négatifs anormaux Blocages excessifs
Occupation -32% ↓ Alerte HIGH
property-insight.service.ts
Opportunités Risques Optimisations Recommandations
Les week-ends sont réservés à 96% ↓ Augmenter tarifs de 10%
Faible taux de réservation en novembre ↓ Lancer campagne promotionnelle
property-health-score.service.ts
Occupation 25% Revenus 25% Avis 20% Réputation 10% Disponibilité 10% Conformité 10%
90-100 Excellent 75-89 Healthy 50-74 At Risk 0-49 Critical
Property Health 93 Excellent
GET /properties/{id}/intelligence
{
"healthScore":93,
"occupancyForecast":91,
"revenueForecast":52000,
"insights":[]
}
GET /properties/{id}/forecasts
GET /properties/{id}/insights
GET /properties/{id}/health-score
GET /properties/{id}/anomalies
Forecast Accuracy Insight Adoption Health Score Trends Anomaly Resolution Rate
property.intelligence.read property.intelligence.forecast property.intelligence.insights property.intelligence.health property.intelligence.admin
FORECAST_GENERATED ANOMALY_DETECTED PROPERTY_INSIGHT_CREATED HEALTH_SCORE_UPDATED INTELLIGENCE_DASHBOARD_VIEWED
Toutes les heures Anomaly Detection Toutes les nuits Forecast Generation Health Score Refresh Insight Generation
À la fin du Sprint 4, la plateforme dispose désormais de :
✓ Catalogue Propriétés ✓ Amenities ✓ Images Enterprise ✓ Disponibilités ✓ Channel Manager ✓ Tarification ✓ Revenue Management ✓ Policies ✓ Legal Compliance ✓ Reviews ✓ Reputation Intelligence ✓ Search Engine ✓ AI Discovery ✓ Analytics ✓ Property Intelligence
Compatible avec :
Reservations Booking Workflow Payments Check-In Guest Journey
Compatible avec :
AI Platform Knowledge Graph LLM Agents Predictive Intelligence
Compatible avec :
Enterprise Property Platform AI Revenue Management Predictive Operations Global Hospitality OS
Le Sprint 4-J.2 est terminé lorsque :
✓ Prévision occupation ✓ Prévision revenus ✓ Prévision demande ✓ Détection anomalies ✓ Insights IA ✓ Property Health Score ✓ Dashboard Intelligence ✓ Audit intégré
PropertyForecast PropertyInsight PropertyHealthScore OccupancyForecastService RevenueForecastService DemandForecastService AnomalyDetectionService PropertyInsightService PropertyHealthScoreService Property Intelligence Platform