Implémenter le premier domaine métier CRM complet de la plateforme.
Le domaine :
Customer
deviendra le pivot central de :
Réservations Facturation CRM Marketing Communication Programme de fidélité Revenue Management
Customer CustomerAddress CustomerDocument CustomerTag CustomerNote CustomerCommunication CustomerPreference
CRUD Clients Profil Client Adresses Documents Tags CRM Notes internes Historique communications Préférences client Segmentation Fidélisation
Customer ├── Addresses ├── Documents ├── Tags ├── Notes ├── Communications ├── Preferences ├── Reservations ├── Invoices └── CRM Activities
Modèle CRM Prisma
Customer CustomerAddress CustomerPreference
CRUD Clients
CustomerModule CustomerController CustomerService
Adresses Client
CustomerAddressModule
Documents Client
CustomerDocument Upload Stockage
Tags CRM
CustomerTag Segmentation
Notes CRM
CustomerNote
Communications
CustomerCommunication
Préférences Client
CustomerPreference
Recherche CRM Avancée
Filtres Segmentation Recherche globale
Vue CRM Enterprise
Dashboard Client Vue 360° Historique complet
Créer le socle CRM principal de la plateforme.
Ces modèles deviendront les fondations de :
Reservations CRM Marketing Facturation Communication Fidélisation Revenue Management
Customer ├── CustomerAddress ├── CustomerPreference ├── Reservations ├── Invoices ├── Notes ├── Tags ├── Documents └── Communications
model Customer {
id String
@id
@default(uuid())
tenantId String
customerNumber String?
externalReference String?
type String
@default("INDIVIDUAL")
status String
@default("ACTIVE")
firstName String
lastName String
companyName String?
email String?
phone String?
mobilePhone String?
dateOfBirth DateTime?
nationality String?
language String?
source String?
acquisitionChannel String?
notes String?
vip Boolean
@default(false)
blacklisted Boolean
@default(false)
marketingConsent Boolean
@default(false)
emailVerified Boolean
@default(false)
phoneVerified Boolean
@default(false)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
addresses CustomerAddress[]
preferences CustomerPreference?
@@index([tenantId])
@@index([status])
@@index([email])
@@index([phone])
@@index([vip])
@@index([createdAt])
@@unique([
tenantId,
customerNumber
])
}
model CustomerAddress {
id String
@id
@default(uuid())
customerId String
label String
addressLine1 String
addressLine2 String?
postalCode String
city String
state String?
country String
latitude Decimal?
@db.Decimal(10,7)
longitude Decimal?
@db.Decimal(10,7)
formattedAddress String?
placeId String?
isDefault Boolean
@default(false)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
@@index([customerId])
@@index([country])
@@index([city])
@@index([isDefault])
}
model CustomerPreference {
id String
@id
@default(uuid())
customerId String
@unique
preferredLanguage String?
preferredCurrency String?
preferredChannel String?
prefersEmail Boolean
@default(true)
prefersSms Boolean
@default(false)
prefersPhone Boolean
@default(false)
acceptsMarketing Boolean
@default(false)
acceptsNewsletter Boolean
@default(false)
acceptsPromotions Boolean
@default(false)
acceptsSurveys Boolean
@default(false)
loyaltyEligible Boolean
@default(true)
vipProgramEligible Boolean
@default(false)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
@@index([preferredLanguage])
@@index([preferredCurrency])
@@index([acceptsMarketing])
}
customers Customer[]
reservations Reservation[]
customerId String?
customer Customer?
@relation(
fields:[customerId],
references:[id]
)
Relations futures :
notes CustomerNote[] documents CustomerDocument[] communications CustomerCommunication[] tags CustomerTag[]
Ces modèles arriveront dans :
Sprint 3-D Sprint 3-E Sprint 3-F Sprint 3-G
Déjà présents :
source acquisitionChannel marketingConsent
Website Booking Airbnb Google Ads Facebook Referral Direct
vip loyaltyEligible vipProgramEligible
Loyalty Program Reward Engine Customer Segmentation
npx prisma migrate dev \
--name crm_customer_core
npx prisma generate
npx prisma validate
The schema at prisma/schema.prisma is valid
\d "Customer" \d "CustomerAddress" \d "CustomerPreference"
Customer │ ├── Profil CRM │ ├── Préférences │ ├── Adresses │ ├── Réservations │ ├── Marketing │ └── Fidélité
Le Sprint 3-A.1 est terminé lorsque :
✓ Customer créé ✓ CustomerAddress créée ✓ CustomerPreference créée ✓ Tenant relation ajoutée ✓ Reservation relation préparée ✓ Migration exécutée ✓ Prisma Client généré ✓ Validation Prisma verte
Customer CustomerAddress CustomerPreference Migration crm_customer_core Prisma Client mis à jour
Faire évoluer le modèle CRM Core vers un modèle Enterprise capable de supporter :
CRM avancé Segmentation Marketing Automation Fidélisation Analyse comportementale Revenue Management RGPD Conformité
Customer ├── Profile ├── Preferences ├── Addresses ├── Segmentation ├── Scoring ├── Risk ├── Lifetime Value ├── GDPR ├── Communications ├── Reservations └── CRM History
enum CustomerStatus {
LEAD
ACTIVE
VIP
INACTIVE
BLOCKED
BLACKLISTED
}
Dans :
model Customer
status String
status CustomerStatus
@default(ACTIVE)
enum CustomerSource {
WEBSITE
DIRECT
AIRBNB
BOOKING
EXPEDIA
VRBO
GOOGLE
FACEBOOK
INSTAGRAM
REFERRAL
PARTNER
IMPORT
OTHER
}
source String?
source CustomerSource?
Dans :
model Customer
customerScore Int
@default(0)
engagementScore Int
@default(0)
satisfactionScore Int
@default(0)
loyaltyScore Int
@default(0)
Segmentation CRM Marketing Fidélisation
lifetimeValue Decimal?
@db.Decimal(14,2)
totalReservations Int
@default(0)
totalRevenue Decimal?
@db.Decimal(14,2)
averageBasket Decimal?
@db.Decimal(14,2)
lastReservationAt DateTime?
Pour :
Revenue Management Business Intelligence Customer Analytics
enum CustomerRiskLevel {
LOW
MEDIUM
HIGH
CRITICAL
}
Dans Customer :
riskLevel CustomerRiskLevel
@default(LOW)
riskScore Int
@default(0)
fraudFlag Boolean
@default(false)
Fraude Paiements Réservations Sécurité
enum CustomerSegment {
STANDARD
FREQUENT
VIP
CORPORATE
OWNER
PARTNER
BLACKLISTED
}
segment CustomerSegment
@default(STANDARD)
Campagnes CRM Automatisation Fidélisation
Dans Customer :
gdprConsentGiven Boolean
@default(false)
gdprConsentAt DateTime?
marketingConsentAt DateTime?
privacyPolicyAccepted Boolean
@default(false)
privacyPolicyAcceptedAt DateTime?
dataRetentionUntil DateTime?
Pour :
Consent ComplianceAudit RetentionPolicy
model CustomerHistory {
id String
@id
@default(uuid())
customerId String
changedField String
oldValue String?
newValue String?
changedBy String?
changedAt DateTime
@default(now())
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
@@index([customerId])
@@index([changedAt])
}
Dans :
model Customer
history CustomerHistory[]
Ajouter dans Customer :
firstReservationAt DateTime? lastCommunicationAt DateTime? lastActivityAt DateTime?
Customer Score Engagement Score Loyalty Score Risk Score
seront alimentés par :
Reservations Invoices CRM Activities Communications
preferredPropertyType String? preferredStayDuration Int? preferredDestination String? preferredTravelSeason String?
Marketing IA Recommandations Segmentation Campagnes
Dans Customer :
@@index([segment]) @@index([customerScore]) @@index([riskLevel]) @@index([source]) @@index([lifetimeValue]) @@index([lastReservationAt]) @@index([gdprConsentGiven])
npx prisma migrate dev \
--name customer_enterprise
npx prisma generate
npx prisma validate
The schema at prisma/schema.prisma is valid
Customer ├── Profil ├── Adresses ├── Préférences ├── Segmentation ├── Score ├── Lifetime Value ├── Risque ├── RGPD ├── Historique ├── Marketing └── Fidélisation
Le Sprint 3-A.2 est terminé lorsque :
✓ CustomerStatus ✓ CustomerSource ✓ CustomerScore ✓ CustomerLifetimeValue ✓ CustomerRisk ✓ CustomerSegment ✓ RGPD ✓ CustomerHistory ✓ Index CRM ✓ Migration exécutée
Customer Enterprise CustomerHistory CustomerStatus CustomerSource CustomerSegment CustomerRiskLevel Migration customer_enterprise
Implémenter le premier module CRM métier complet basé sur :
Customer
avec :
CRUD Clients Recherche CRM Pagination Tri Filtres Multi-Tenant Security Swagger
src/modules/customers
customers ├── application │ │ └── dto │ │ ├── create-customer.dto.ts │ ├── update-customer.dto.ts │ ├── customer-query.dto.ts │ └── customer-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer.controller.ts │ └── customer.module.ts
create-customer.dto.ts
export class CreateCustomerDto {
firstName: string;
lastName: string;
email?: string;
phone?: string;
mobilePhone?: string;
companyName?: string;
language?: string;
source?: string;
marketingConsent?: boolean;
}
update-customer.dto.ts
export class UpdateCustomerDto
extends PartialType(
CreateCustomerDto
) {}
customer-query.dto.ts
export class CustomerQueryDto {
page?: number = 1;
limit?: number = 25;
search?: string;
status?: string;
segment?: string;
source?: string;
vip?: boolean;
sortBy?: string =
'createdAt';
sortOrder?: 'asc' | 'desc' =
'desc';
}
customer-response.dto.ts
export class CustomerResponseDto {
id: string;
firstName: string;
lastName: string;
email?: string;
phone?: string;
status: string;
segment: string;
customerScore: number;
lifetimeValue?: number;
vip: boolean;
createdAt: Date;
}
customer.service.ts
@Injectable()
export class CustomerService {
constructor(
private readonly prisma:
PrismaService
) {}
}
findAll() findOne() create() update() remove()
Aucune requête CRM ne doit sortir du tenant.
where: {
tenantId:
currentTenantId
}
Dans :
Toutes les requêtes
OR: [
{
firstName: {
contains: search,
mode: 'insensitive'
}
},
{
lastName: {
contains: search,
mode: 'insensitive'
}
},
{
email: {
contains: search,
mode: 'insensitive'
}
},
{
companyName: {
contains: search,
mode: 'insensitive'
}
}
]
status segment source vip
if (query.vip !== undefined) {
where.vip =
query.vip;
}
const skip = (page - 1) * limit;
skip, take: limit
orderBy: {
[query.sortBy]:
query.sortOrder
}
async findAll(
tenantId: string,
query:
CustomerQueryDto
)
return {
data:
customers,
meta: {
page,
limit,
total
}
};
async findOne( tenantId: string, id: string )
where: {
id,
tenantId
}
throw new NotFoundException( 'Customer not found' );
customerNumber =
`CUS-${Date.now()}`
await prisma.customer.create({
data: {
tenantId,
customerNumber,
...dto
}
});
await prisma.customerPreference.create({
data: {
customerId:
customer.id
}
});
Customer existe + Même tenant
await prisma.customer.update(...)
Ne jamais supprimer réellement.
deletedAt: new Date()
await prisma.customer.update({
where: {
id
},
data: {
deletedAt:
new Date(),
status:
'INACTIVE'
}
});
customer.controller.ts
@ApiTags('Customers')
@Controller('customers')
@UseGuards(
JwtAuthGuard
)
@ApiBearerAuth()
@Get()
Recherche Pagination Filtres
@Get(':id')
@Post()
@Put(':id')
@Delete(':id')
customer.module.ts
@Module({
imports: [
PrismaModule,
AuthModule
],
controllers: [
CustomerController
],
providers: [
CustomerService
],
exports: [
CustomerService
]
})
export class CustomerModule {}
imports: [ ... CustomerModule ]
GET /customers
GET /customers/{id}
POST /customers
PUT /customers/{id}
DELETE /customers/{id}
GET /customers?page=1&limit=25
{
"data": [
{
"id": "xxx",
"firstName": "Jean",
"lastName": "Dupont",
"segment": "VIP"
}
],
"meta": {
"page": 1,
"limit": 25,
"total": 132
}
}
{
"firstName": "Jean",
"lastName": "Dupont",
"email": "jean@demo.fr",
"marketingConsent": true
}
Customer créé CustomerPreference créée Numéro CRM généré
Ce module servira directement à :
CustomerAddress CustomerDocument CustomerTag CustomerNote CustomerCommunication
Le Sprint 3-B.1 est terminé lorsque :
✓ CustomerModule créé ✓ CustomerController créé ✓ CustomerService créé ✓ CRUD Clients opérationnel ✓ Recherche CRM ✓ Pagination ✓ Filtres ✓ Multi-Tenant Security ✓ Swagger documenté ✓ Soft Delete
CustomerModule CustomerController CustomerService CreateCustomerDto UpdateCustomerDto CustomerQueryDto CustomerResponseDto
Transformer le CRUD Clients en véritable CRM Enterprise.
À l'issue de cette étape :
✓ Recherche avancée ✓ Segmentation dynamique ✓ Smart Segments ✓ Scores CRM ✓ Filtres combinés ✓ Export CSV ✓ Vues enregistrées ✓ CRM Analytics Ready
Customer ↓ CRM Search Engine ↓ Dynamic Filters ↓ Segmentation Engine ↓ Smart Segments ↓ Export / Campaigns / Analytics
model SavedCustomerView {
id String
@id
@default(uuid())
tenantId String
userId String
name String
description String?
filters Json
isDefault Boolean
@default(false)
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 CustomerSegmentRule {
id String
@id
@default(uuid())
tenantId String
name String
code String
@unique
description String?
conditions Json
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@index([tenantId])
@@index([active])
}
npx prisma migrate dev \
--name crm_segmentation
npx prisma generate
customerScoreMin?: number; customerScoreMax?: number; lifetimeValueMin?: number; lifetimeValueMax?: number; riskLevel?: string; country?: string; language?: string; createdFrom?: Date; createdTo?: Date; lastReservationFrom?: Date; lastReservationTo?: Date; marketingConsent?: boolean; segment?: string; vip?: boolean;
AND: [ status, segment, vip, marketingConsent, riskLevel ]
OR: [ firstName, lastName, email, companyName, customerNumber ]
VIP ET Score > 80 ET LifetimeValue > 5000€ ET MarketingConsent = true
Liste filtrée
crm-score.service.ts
calculateCustomerScore() calculateEngagementScore() calculateLoyaltyScore()
Réservations +30 Paiements +20 Avis positifs +15 Ancienneté +20 Marketing +15
100
0-25 LOW
26-50 NORMAL
51-75 GOOD
76-100 EXCELLENT
crm-segmentation.service.ts
VIP Frequent Travelers Corporate High Revenue Inactive High Risk Marketing Qualified
LifetimeValue > 10000 ET Score > 80
Dernière réservation > 365 jours
CompanyName != null
create-saved-view.dto.ts
export class CreateSavedViewDto {
name: string;
description?: string;
filters: Record<
string,
unknown
>;
}
GET /customers/views
POST /customers/views
DELETE /customers/views/{id}
Mes VIP Clients inactifs Corporate France Top Revenue
crm-export.service.ts
exportCsv() exportExcel()
CustomerNumber FirstName LastName Email Segment Score LifetimeValue Status
GET /customers/export
Même filtres CRM
GET /customers/search
GET /customers/export
GET /customers/views
POST /customers/views
DELETE /customers/views/{id}
GET /customers/segments
POST /customers/segments/recalculate
GET /customers/search
segment=VIP scoreMin=80 marketingConsent=true country=FR
Liste CRM filtrée
Toutes les requêtes :
tenantId
doivent être injectées automatiquement.
SavedCustomerView CustomerSegmentRule Customer Export
CRM_SEARCH CRM_EXPORT CRM_SEGMENT_UPDATE CRM_VIEW_CREATE
dans :
AuditLog
Campaign CampaignRecipient MarketingSegment AutomationRule Recommendation
Compatible avec :
Customer Scoring Recommendations Lead Scoring Predictive Segmentation
Le Sprint 3-B.2 est terminé lorsque :
✓ Recherche avancée ✓ Segmentation dynamique ✓ Smart Segments ✓ CRM Score Engine ✓ Export CSV ✓ Vues enregistrées ✓ Audit CRM ✓ Swagger documenté
SavedCustomerView CustomerSegmentRule CrmScoreService CrmSegmentationService CrmExportService CRM Search API CRM Segments API CRM Views API
Implémenter la gestion complète des adresses CRM client basée sur :
CustomerAddress
avec :
CRUD Adresses Adresse principale Géolocalisation Validation avancée Multi-Tenant Security Historisation CRM
src/modules/customer-addresses
customer-addresses ├── application │ │ └── dto │ │ ├── create-customer-address.dto.ts │ ├── update-customer-address.dto.ts │ └── customer-address-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer-address.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer-address.controller.ts │ └── customer-address.module.ts
create-customer-address.dto.ts
export class CreateCustomerAddressDto {
label: string;
addressLine1: string;
addressLine2?: string;
postalCode: string;
city: string;
state?: string;
country: string;
isDefault?: boolean;
}
update-customer-address.dto.ts
export class UpdateCustomerAddressDto
extends PartialType(
CreateCustomerAddressDto
) {}
customer-address-response.dto.ts
export class CustomerAddressResponseDto {
id: string;
label: string;
addressLine1: string;
postalCode: string;
city: string;
country: string;
latitude?: number;
longitude?: number;
formattedAddress?: string;
isDefault: boolean;
createdAt: Date;
}
customer-address.service.ts
@Injectable()
export class CustomerAddressService {
constructor(
private readonly prisma:
PrismaService,
private readonly geocodingService:
GeocodingService
) {}
}
findAll() create() update() remove() setDefault()
Le client appartient au tenant.
const customer =
await prisma.customer.findFirst({
where: {
id: customerId,
tenantId
}
});
throw new NotFoundException( 'Customer not found' );
async findAll( tenantId: string, customerId: string )
return prisma.customerAddress.findMany({
where: {
customerId
},
orderBy: [
{
isDefault:
'desc'
},
{
createdAt:
'desc'
}
]
});
private async resetDefaultAddress( customerId: string )
await prisma.customerAddress.updateMany({
where: {
customerId
},
data: {
isDefault: false
}
});
dto.isDefault === true
await resetDefaultAddress( customerId );
count === 0
isDefault = true
const fullAddress =
`${dto.addressLine1}
${dto.postalCode}
${dto.city}
${dto.country}`;
await geocodingService.geocode( fullAddress );
latitude longitude formattedAddress placeId
Si :
OpenStreetMap indisponible
Créer quand même l'adresse.
latitude = null longitude = null
SUPPORTED_COUNTRIES
PostalCodeValidator
Ville Code postal Pays
sont cohérents avec :
GeocodingResult
throw new BadRequestException( 'Address validation failed' );
Lors de :
CREATE UPDATE DELETE
await prisma.customerHistory.create({
data: {
customerId,
changedField:
'address',
oldValue:
oldAddress,
newValue:
newAddress
}
});
CUSTOMER_ADDRESS_CREATED CUSTOMER_ADDRESS_UPDATED CUSTOMER_ADDRESS_DELETED CUSTOMER_ADDRESS_DEFAULT_CHANGED
Customer Adresse Tenant
avant toute modification.
dto.isDefault = true
resetDefaultAddress()
update()
Ajouter dans Prisma :
deletedAt DateTime?
à :
CustomerAddress
deletedAt: new Date()
customer-address.controller.ts
@ApiTags('Customer Addresses')
@Controller(
'customers/:id/addresses'
)
@UseGuards(
JwtAuthGuard
)
@ApiBearerAuth()
GET /customers/{id}/addresses
POST /customers/{id}/addresses
PUT /customers/{id}/addresses/{addressId}
DELETE /customers/{id}/addresses/{addressId}
customer-address.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
GeocodingModule
],
controllers: [
CustomerAddressController
],
providers: [
CustomerAddressService
],
exports: [
CustomerAddressService
]
})
export class CustomerAddressModule {}
GET /customers/{id}/addresses
POST /customers/{id}/addresses
PUT /customers/{id}/addresses/{addressId}
DELETE /customers/{id}/addresses/{addressId}
{
"label": "Residence",
"addressLine1": "10 Rue Victor Hugo",
"postalCode": "75001",
"city": "Paris",
"country": "FR",
"isDefault": true
}
Adresse créée Adresse principale définie Coordonnées GPS calculées Historique CRM enregistré
Les adresses client seront utilisées par :
Reservations Invoices Documents Marketing CRM Analytics
Le Sprint 3-C.1 est terminé lorsque :
✓ CustomerAddressModule créé ✓ CustomerAddressController créé ✓ CustomerAddressService créé ✓ CRUD adresses opérationnel ✓ Adresse principale opérationnelle ✓ Géolocalisation opérationnelle ✓ Validation avancée ✓ Historisation CRM ✓ AuditLog intégré ✓ Swagger documenté
CustomerAddressModule CustomerAddressController CustomerAddressService CreateCustomerAddressDto UpdateCustomerAddressDto CustomerAddressResponseDto
Transformer le système d'adresses CRM en moteur géospatial Enterprise.
À l'issue de cette étape :
✓ Multi-adresses ✓ Zones géographiques ✓ Distance client ✓ Recherche par rayon ✓ Clustering géographique ✓ Cartographie CRM ✓ Heatmaps ✓ Géo-segmentation
Customer ↓ CustomerAddress ↓ Geo Engine ↓ Spatial Queries ↓ Maps ↓ CRM Analytics
model GeoZone {
id String
@id
@default(uuid())
tenantId String
code String
name String
description String?
zoneType GeoZoneType
country String?
region String?
city String?
polygon Json?
centerLatitude Decimal?
@db.Decimal(10,7)
centerLongitude Decimal?
@db.Decimal(10,7)
radiusKm Decimal?
@db.Decimal(10,2)
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@index([tenantId])
@@index([zoneType])
@@index([active])
}
enum GeoZoneType {
COUNTRY
REGION
CITY
CUSTOM
SALES
MARKETING
DELIVERY
}
geoHash String?
geoValidated Boolean
@default(false)
geoValidatedAt DateTime?
timezone String?
district String?
npx prisma migrate dev \
--name crm_geospatial
npx prisma generate
src/modules/geospatial
geospatial ├── domain │ │ └── services │ │ ├── geo-distance.service.ts │ │ ├── geo-search.service.ts │ │ ├── geo-clustering.service.ts │ │ └── geo-zone.service.ts │ └── geospatial.module.ts
npm install geolib npm install ngeohash
geo-distance.service.ts
calculateDistance() calculateTravelRadius() findNearestCustomers()
getDistance(
{
latitude: lat1,
longitude: lng1
},
{
latitude: lat2,
longitude: lng2
}
)
Distance en kilomètres
Client ↔ Propriété Client ↔ Agence Client ↔ Région Client ↔ Zone commerciale
GET /customers/search/radius
latitude longitude radiusKm
findCustomersInRadius( tenantId, lat, lng, radiusKm )
Tous les clients dans le rayon demandé
Paris 50 km
Clients Paris Clients Versailles Clients Saint-Denis
geo-clustering.service.ts
clusterCustomers() clusterByCity() clusterByRegion()
Ville Région Pays Distance Segment CRM
{
"cluster":"Paris",
"customers":421
}
customer-map-point.dto.ts
export class CustomerMapPointDto {
customerId: string;
customerNumber: string;
latitude: number;
longitude: number;
city: string;
segment: string;
customerScore: number;
}
GET /customers/map
Points cartographiques CRM
Compatible :
Leaflet Mapbox Google Maps OpenLayers
GET /customers/heatmap
Densité clients par zone géographique
Nombre clients par géohash ou par ville
[
{
"city":"Paris",
"count":421
},
{
"city":"Lyon",
"count":187
}
]
GET /geo-zones
POST /geo-zones
PUT /geo-zones/{id}
DELETE /geo-zones/{id}
Zone Paris ↓ Clients VIP ↓ Campagne Marketing
Adresse Client ↓ GeoZone Matching ↓ Segment Géographique ↓ Marketing
GET /customers/geographic-statistics
{
"countries":12,
"regions":38,
"cities":142,
"topCity":"Paris"
}
Top villes Top régions Répartition pays Répartition segments Lifetime Value géographique
Dans :
AuditLog
CRM_GEO_SEARCH CRM_MAP_VIEW CRM_HEATMAP_VIEW CRM_ZONE_CREATED CRM_ZONE_UPDATED
Toutes les requêtes :
tenantId
doivent filtrer :
GeoZone Customer CustomerAddress
Compatible avec :
Campaign MarketingSegment CampaignRecipient
Compatible avec :
Recommendation AutomationScenario AI Segmentation
Le Sprint 3-C.2 est terminé lorsque :
✓ GeoZone créée ✓ Distance Client ✓ Recherche par rayon ✓ Clustering CRM ✓ Cartographie CRM ✓ Heatmaps ✓ Dashboard géographique ✓ Multi-Tenant Security ✓ Swagger documenté
GeoZone GeoDistanceService GeoSearchService GeoClusteringService GeoZoneService CRM Map API CRM Heatmap API CRM Geographic Analytics
Implémenter la gestion documentaire CRM complète basée sur :
CustomerDocument
avec :
Upload Stockage MinIO/S3 Catégorisation Validation documentaire Historisation Audit RGPD
Customer ↓ CustomerDocument ↓ StorageService ↓ MinIO / S3 ↓ CRM / Reservations / Compliance
model CustomerDocument {
id String
@id
@default(uuid())
tenantId String
customerId String
documentType CustomerDocumentType
category String?
name String
originalFilename String
storagePath String
mimeType String
fileSize Int
checksum String?
version Int
@default(1)
status DocumentStatus
@default(PENDING)
expiresAt DateTime?
verifiedAt DateTime?
verifiedBy String?
uploadedBy String?
metadata Json?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
@@index([tenantId])
@@index([customerId])
@@index([documentType])
@@index([status])
@@index([expiresAt])
}
enum CustomerDocumentType {
IDENTITY_CARD
PASSPORT
DRIVING_LICENSE
VISA
RESIDENCE_PERMIT
CONTRACT
INVOICE
PROOF_OF_ADDRESS
INSURANCE
TAX_DOCUMENT
OTHER
}
enum DocumentStatus {
PENDING
VERIFIED
REJECTED
EXPIRED
ARCHIVED
}
Dans :
model Customer
documents CustomerDocument[]
npx prisma migrate dev \
--name customer_documents
npx prisma generate
src/modules/customer-documents ├── application │ │ └── dto │ │ ├── upload-customer-document.dto.ts │ └── customer-document-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer-document.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer-document.controller.ts │ └── customer-document.module.ts
upload-customer-document.dto.ts
export class UploadCustomerDocumentDto {
documentType:
CustomerDocumentType;
category?: string;
expiresAt?: Date;
}
customer-document-response.dto.ts
export class CustomerDocumentResponseDto {
id: string;
name: string;
documentType: string;
status: string;
mimeType: string;
fileSize: number;
createdAt: Date;
}
shared/constants document-mime-types.ts
export const ALLOWED_DOCUMENT_TYPES = [ 'application/pdf', 'image/jpeg', 'image/png', 'image/webp' ];
export const MAX_DOCUMENT_SIZE = 20 * 1024 * 1024;
mimetype size
EXE ZIP JS BAT SH
customer-document.service.ts
@Injectable()
export class CustomerDocumentService {
constructor(
private readonly prisma:
PrismaService,
private readonly storageService:
StorageService
) {}
}
findAll() upload() remove() verify() generateDownloadUrl()
customers/
{customerId}/
documents/
{uuid}
const storagePath =
await storageService
.uploadDocument(
file,
customerId
);
await prisma.customerDocument.create({
data: {
tenantId,
customerId,
documentType,
storagePath,
mimeType:
file.mimetype,
fileSize:
file.size,
status:
'PENDING'
}
});
generateDownloadUrl()
Presigned URL Expiration 1h
DOCUMENT_UPLOADED DOCUMENT_VERIFIED DOCUMENT_DELETED
changedField: 'document'
Dans :
AuditLog
CUSTOMER_DOCUMENT_UPLOAD CUSTOMER_DOCUMENT_VERIFY CUSTOMER_DOCUMENT_DELETE
customer-document.controller.ts
@ApiTags( 'Customer Documents' ) @Controller( 'customers/:id/documents' ) @UseGuards( JwtAuthGuard ) @ApiBearerAuth()
GET /customers/{id}/documents
POST /customers/{id}/documents
@UseInterceptors(
FileInterceptor(
'file'
)
)
DELETE /customers/{id}/documents/{documentId}
GET /customers/{id}/documents/{documentId}/download
PUT /customers/{id}/documents/{documentId}/verify
Status ↓ VERIFIED
verifiedAt verifiedBy
Passport Visa Insurance
Créer alertes :
30 jours avant expiration
customer-document.module.ts
@Module({
imports: [
PrismaModule,
AuthModule,
StorageModule
],
controllers: [
CustomerDocumentController
],
providers: [
CustomerDocumentService
],
exports: [
CustomerDocumentService
]
})
export class CustomerDocumentModule {}
GET /customers/{id}/documents
POST /customers/{id}/documents
DELETE /customers/{id}/documents/{documentId}
GET /customers/{id}/documents/{documentId}/download
PUT /customers/{id}/documents/{documentId}/verify
Passeport.pdf
Document stocké MinIO Status=PENDING Historique CRM créé Audit créé
Compatible avec :
Consent ComplianceAudit RetentionPolicy DataClassification
Compatible avec :
OCR Document AI Extraction automatique Classification automatique
Le Sprint 3-D.1 est terminé lorsque :
✓ CustomerDocument créé ✓ Upload opérationnel ✓ MinIO/S3 intégré ✓ Validation documentaire ✓ Vérification documentaire ✓ AuditLog intégré ✓ Historisation CRM ✓ URLs sécurisées ✓ Swagger documenté
CustomerDocument CustomerDocumentModule CustomerDocumentController CustomerDocumentService Document Validation MinIO Integration Secure Download URLs
Faire évoluer le système documentaire CRM vers une GED Enterprise complète.
À l'issue de cette étape :
✓ Versioning ✓ OCR ✓ Classification automatique ✓ Extraction de données ✓ Expiration automatique ✓ Workflow de validation ✓ Signature électronique ✓ Conformité RGPD ✓ Préparation ISO27001
Document Upload ↓ OCR Engine ↓ Classification Engine ↓ Extraction Engine ↓ Validation Workflow ↓ Storage ↓ Compliance
parentDocumentId String?
documentVersion Int
@default(1)
isLatestVersion Boolean
@default(true)
supersededAt DateTime?
parentDocument CustomerDocument?
@relation(
"DocumentVersions",
fields:[parentDocumentId],
references:[id]
)
versions CustomerDocument[]
@relation(
"DocumentVersions"
)
ocrStatus OcrStatus
@default(PENDING)
ocrText String?
ocrExecutedAt DateTime?
classificationScore Float? classificationSource String? detectedDocumentType String?
extractedData Json? extractionStatus String?
signatureRequired Boolean
@default(false)
signedAt DateTime?
signatureProvider String?
signatureReference String?
enum OcrStatus {
PENDING
PROCESSING
COMPLETED
FAILED
}
npx prisma migrate dev \
--name customer_documents_enterprise
npx prisma generate
model DocumentWorkflow {
id String
@id
@default(uuid())
tenantId String
name String
documentType String
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
steps DocumentWorkflowStep[]
}
model DocumentWorkflowStep {
id String
@id
@default(uuid())
workflowId String
name String
stepOrder Int
validatorRole String?
required Boolean
@default(true)
workflow DocumentWorkflow
@relation(
fields:[workflowId],
references:[id],
onDelete:Cascade
)
@@index([workflowId])
@@index([stepOrder])
}
model CustomerDocumentWorkflow {
id String
@id
@default(uuid())
documentId String
currentStep Int
@default(1)
status WorkflowStatus
@default(PENDING)
startedAt DateTime
@default(now())
completedAt DateTime?
document CustomerDocument
@relation(
fields:[documentId],
references:[id]
)
}
enum WorkflowStatus {
PENDING
IN_PROGRESS
APPROVED
REJECTED
CANCELLED
}
src/modules/document-ai
document-ai ├── domain │ │ └── services │ │ ├── ocr.service.ts │ │ ├── document-classifier.service.ts │ │ ├── extraction.service.ts │ │ └── expiration-monitor.service.ts │ └── document-ai.module.ts
processDocument() extractText() detectLanguage()
Connecteurs futurs :
Tesseract AWS Textract Google Vision Azure Form Recognizer
Upload ↓ OCR ↓ Classification ↓ Extraction ↓ Validation ↓ Archive
classify() detectPassport() detectInvoice() detectContract()
Passeport ↓ IDENTITY_DOCUMENT
classificationScore: 0.98
detectedDocumentType classificationScore classificationSource
extractIdentityData() extractInvoiceData() extractContractData()
Nom Prénom Nationalité Date expiration Numéro
extractedData Json
Numéro facture Montant Date TVA
checkExpiringDocuments() markExpiredDocuments()
Tous les jours ↓ Expiration < 30 jours ↓ Notification ↓ Alert
EXPIRED
automatiquement.
SignatureProvider
DocuSign Yousign Adobe Sign
Document ↓ Signature Request ↓ Signed ↓ Archive
POST /customers/{id}/documents/{documentId}/sign
GET /customers/{id}/documents/{documentId}/signature
POST /documents/{id}/ocr
POST /documents/{id}/classify
POST /documents/{id}/extract
GET /documents/{id}/workflow
PUT /documents/{id}/approve
PUT /documents/{id}/reject
documents.read documents.write documents.verify documents.approve documents.sign
DOCUMENT_OCR DOCUMENT_CLASSIFIED DOCUMENT_EXTRACTED DOCUMENT_APPROVED DOCUMENT_REJECTED DOCUMENT_SIGNED DOCUMENT_EXPIRED
Compatible avec :
PII IDENTITY FINANCIAL LEGAL CONFIDENTIAL
Compatible avec :
RetentionPolicy ComplianceAudit GDPR
GET /documents/statistics
{
"totalDocuments": 5234,
"verifiedDocuments": 4987,
"expiredDocuments": 74,
"pendingValidation": 32,
"pendingSignatures": 11
}
GET /documents/search
Type Statut Date expiration Client OCR Workflow
Compatible avec :
KnowledgeDocument Recommendation AI Assistant Document AI
Compatible avec :
ComplianceAudit DataClassification RetentionPolicy SecurityIncident
Le Sprint 3-D.2 est terminé lorsque :
✓ Versioning ✓ OCR ✓ Classification automatique ✓ Extraction de données ✓ Expiration automatique ✓ Workflow validation ✓ Signature électronique ✓ Audit GED ✓ Conformité préparée
DocumentWorkflow DocumentWorkflowStep CustomerDocumentWorkflow OcrService DocumentClassifierService ExtractionService ExpirationMonitorService Enterprise GED API
Implémenter un moteur de tags CRM permettant :
Segmentation Classification Automatisation Marketing CRM Analytics Personnalisation
basé sur :
CustomerTag CustomerTagAssignment
Customer ↓ CustomerTagAssignment ↓ CustomerTag ↓ CRM Segmentation ↓ Campaigns ↓ Automation
model CustomerTag {
id String
@id
@default(uuid())
tenantId String
name String
code String
description String?
color String?
icon String?
category String?
tagType CustomerTagType
@default(MANUAL)
active Boolean
@default(true)
usageCount Int
@default(0)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
assignments CustomerTagAssignment[]
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([tagType])
@@index([active])
}
model CustomerTagAssignment {
id String
@id
@default(uuid())
customerId String
tagId String
assignedBy String?
assignedAt DateTime
@default(now())
source String?
@default("MANUAL")
expiresAt DateTime?
metadata Json?
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
tag CustomerTag
@relation(
fields:[tagId],
references:[id],
onDelete:Cascade
)
@@unique([
customerId,
tagId
])
@@index([customerId])
@@index([tagId])
@@index([assignedAt])
}
enum CustomerTagType {
MANUAL
DYNAMIC
SYSTEM
MARKETING
LOYALTY
RISK
}
tags CustomerTagAssignment[]
npx prisma migrate dev \
--name customer_tags
npx prisma generate
src/modules/customer-tags ├── application │ │ └── dto │ │ ├── create-customer-tag.dto.ts │ │ ├── assign-customer-tag.dto.ts │ │ └── customer-tag-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer-tag.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer-tag.controller.ts │ └── customer-tag.module.ts
create-customer-tag.dto.ts
export class CreateCustomerTagDto {
name: string;
code: string;
description?: string;
color?: string;
icon?: string;
category?: string;
tagType?: string;
}
assign-customer-tag.dto.ts
export class AssignCustomerTagDto {
tagId: string;
expiresAt?: Date;
}
findTags() createTag() assignTag() removeTag() getCustomerTags() recalculateUsage()
Code unique par tenant
await prisma.customerTag.create({
data: {
tenantId,
...dto
}
});
Customer Tag Même tenant
await prisma.customerTagAssignment.create({
data: {
customerId,
tagId,
assignedBy:
userId
}
});
await prisma.customerTag.update({
where: {
id: tagId
},
data: {
usageCount: {
increment: 1
}
}
});
CustomerTagAssignment
usageCount
dynamicRule Json?
{
"customerScore": {
"gte": 80
}
}
{
"lifetimeValue": {
"gte": 10000
}
}
evaluateDynamicTags()
Customer Updated Reservation Created Score Updated
VIP FREQUENT CORPORATE HIGH_VALUE INACTIVE BLACKLISTED HIGH_RISK NEWSLETTER LOYALTY
Segment = VIP ↓ Tag VIP
Risk = HIGH ↓ Tag HIGH_RISK
customer-tag.controller.ts
@ApiTags('Customer Tags')
@Controller('customers')
GET /customers/tags
POST /customers/tags
POST /customers/{id}/tags
DELETE /customers/{id}/tags/{tagId}
GET /customers/{id}/tags
CustomerQueryDto
tags?: string[];
VIP + NEWSLETTER
Clients correspondants
GET /customers/search
tag=VIP
Marketing Campagnes Automatisation
TAG_ASSIGNED TAG_REMOVED
CUSTOMER_TAG_CREATED CUSTOMER_TAG_ASSIGNED CUSTOMER_TAG_REMOVED CUSTOMER_DYNAMIC_TAG_UPDATED
Campaign CampaignRecipient MarketingSegment MarketingEvent
Compatible avec :
AutomationRule Recommendation AiConversation AutomationScenario
Compatible avec :
Customer Analytics Predictive Segmentation Lead Scoring AI Recommendations
Le Sprint 3-E.1 est terminé lorsque :
✓ CustomerTag créé ✓ CustomerTagAssignment créé ✓ Tags manuels ✓ Tags dynamiques ✓ Segmentation CRM ✓ Recherche par tags ✓ AuditLog intégré ✓ Historisation CRM ✓ Swagger documenté
CustomerTag CustomerTagAssignment CustomerTagModule CustomerTagController CustomerTagService Dynamic Tag Engine
Transformer le moteur de tags CRM en moteur intelligent de segmentation Enterprise.
À l'issue de cette étape :
✓ Tag Rules Engine ✓ Smart Tags IA ✓ Segmentation comportementale ✓ Scoring automatique ✓ Segments prédictifs ✓ Marketing Intelligence ✓ CRM Intelligence Engine
Customer Data ↓ Behavior Analysis ↓ Scoring Engine ↓ Tag Rules Engine ↓ Smart Segments ↓ Marketing Intelligence ↓ Automation
model SmartSegment {
id String
@id
@default(uuid())
tenantId String
name String
code String
description String?
segmentType SmartSegmentType
active Boolean
@default(true)
ruleDefinition Json
estimatedCustomers Int
@default(0)
lastCalculatedAt DateTime?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([segmentType])
@@index([active])
}
model CustomerScore {
id String
@id
@default(uuid())
customerId String
@unique
engagementScore Int
@default(0)
loyaltyScore Int
@default(0)
revenueScore Int
@default(0)
marketingScore Int
@default(0)
riskScore Int
@default(0)
predictiveScore Int
@default(0)
overallScore Int
@default(0)
calculatedAt DateTime
@default(now())
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
}
enum SmartSegmentType {
BEHAVIORAL
REVENUE
LOYALTY
MARKETING
PREDICTIVE
RISK
AI_GENERATED
}
score CustomerScore?
npx prisma migrate dev \
--name crm_smart_segments
npx prisma generate
crm-tag-rules.service.ts
evaluateRules() executeRule() assignTags() removeTags()
{
"conditions": [
{
"field": "lifetimeValue",
"operator": "gte",
"value": 10000
}
],
"action": {
"tag": "HIGH_VALUE"
}
}
eq neq gt gte lt lte contains in notIn
customer-scoring.service.ts
calculateOverallScore() calculateEngagement() calculateLoyalty() calculateRevenue() calculateRisk()
Communications Ouvertures emails Réponses Activité CRM
0 → 100
Ancienneté Réservations Lifetime Value VIP
Total Revenue Average Basket Frequency
smart-tag.service.ts
generateSmartTags() predictCustomerCategory() recommendTags()
HIGH_VALUE FUTURE_VIP AT_RISK LOYAL_CUSTOMER UPSELL_TARGET CHURN_RISK
Lifetime Value élevé + Croissance forte ↓ FUTURE_VIP
behavior-segmentation.service.ts
Frequent Traveler Weekend Traveler Luxury Traveler Business Traveler Seasonal Traveler Family Traveler
Reservations Invoices Communications Preferences
80% réservations Vendredi → Dimanche ↓ Weekend Traveler
predictive-segmentation.service.ts
Future VIP Future Churn Future Corporate Future High Revenue Future Ambassador
Croissance dépenses Fréquence réservations Engagement Satisfaction
Croissance 30% ↓ Future VIP
marketing-intelligence.service.ts
findCampaignTargets() findUpsellTargets() findRetentionTargets() findReferralCandidates()
VIP + Revenue > 5000 ↓ Campagne Premium
CHURN_RISK ↓ Campagne Réactivation
GET /customers/segments/smart GET /customers/tags/smart GET /customers/scoring POST /customers/scoring/recalculate POST /customers/segments/recalculate
{
"customerId":"123",
"overallScore":87,
"smartTags":[
"VIP",
"LOYAL_CUSTOMER",
"UPSELL_TARGET"
]
}
GET /customers/intelligence/dashboard
{
"futureVip":54,
"churnRisk":23,
"upsellTargets":118,
"loyalCustomers":401
}
Segments Scores Tags Prévisions
SMART_TAG_ASSIGNED SEGMENT_RECALCULATED CUSTOMER_SCORE_UPDATED PREDICTIVE_SEGMENT_ASSIGNED
Reservation Created Invoice Paid Customer Updated Campaign Opened
Recalculate Scores Recalculate Tags Recalculate Segments
Compatible avec :
Campaign CampaignRecipient MarketingSegment MarketingEvent
Compatible avec :
Recommendation AutomationRule AutomationScenario AiConversation
Compatible avec :
Enterprise Analytics Customer Intelligence Predictive CRM AI Recommendations
Le Sprint 3-E.2 est terminé lorsque :
✓ SmartSegment créé ✓ CustomerScore créé ✓ Tag Rules Engine ✓ Smart Tags ✓ Segmentation comportementale ✓ Segments prédictifs ✓ Marketing Intelligence ✓ Dashboard CRM Intelligence ✓ Audit intégré
SmartSegment CustomerScore CustomerScoringService SmartTagService BehaviorSegmentationService PredictiveSegmentationService MarketingIntelligenceService CRM Intelligence API
Implémenter le système de notes CRM permettant aux équipes :
Commerciales Support Réservations Finance Marketing Administration
de collaborer autour d'un client.
À l'issue de cette étape :
✓ Notes privées ✓ Notes publiques ✓ Historique CRM ✓ Mentions utilisateurs ✓ Audit ✓ Recherche ✓ Timeline CRM
Customer ↓ CustomerNote ↓ Mentions ↓ Timeline CRM ↓ Audit ↓ Collaboration
model CustomerNote {
id String
@id
@default(uuid())
tenantId String
customerId String
authorId String
title String?
content String
visibility NoteVisibility
@default(PRIVATE)
noteType CustomerNoteType
@default(GENERAL)
pinned Boolean
@default(false)
edited Boolean
@default(false)
editedAt DateTime?
metadata Json?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
author User
@relation(
fields:[authorId],
references:[id]
)
mentions CustomerNoteMention[]
@@index([tenantId])
@@index([customerId])
@@index([authorId])
@@index([visibility])
@@index([createdAt])
}
model CustomerNoteMention {
id String
@id
@default(uuid())
noteId String
userId String
readAt DateTime?
createdAt DateTime
@default(now())
note CustomerNote
@relation(
fields:[noteId],
references:[id],
onDelete:Cascade
)
user User
@relation(
fields:[userId],
references:[id]
)
@@unique([
noteId,
userId
])
@@index([userId])
@@index([noteId])
}
enum NoteVisibility {
PRIVATE
INTERNAL
PUBLIC
}
enum CustomerNoteType {
GENERAL
SALES
SUPPORT
FINANCE
MARKETING
RESERVATION
VIP
RISK
}
Dans :
model Customer
notes CustomerNote[]
npx prisma migrate dev \
--name customer_notes
npx prisma generate
src/modules/customer-notes ├── application │ │ └── dto │ │ ├── create-customer-note.dto.ts │ │ ├── update-customer-note.dto.ts │ │ └── customer-note-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer-note.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer-note.controller.ts │ └── customer-note.module.ts
create-customer-note.dto.ts
export class CreateCustomerNoteDto {
title?: string;
content: string;
visibility?: string;
noteType?: string;
pinned?: boolean;
mentions?: string[];
}
update-customer-note.dto.ts
export class UpdateCustomerNoteDto
extends PartialType(
CreateCustomerNoteDto
) {}
findAll() findOne() create() update() remove() createMentions()
Pinned ↓ Date création DESC
orderBy: [
{
pinned: 'desc'
},
{
createdAt: 'desc'
}
]
Customer Tenant
const note =
await prisma.customerNote.create({
data: {
tenantId,
customerId,
authorId,
...dto
}
});
@john @support @manager
await prisma.customerNoteMention.createMany({
data: [...]
});
IN_APP EMAIL
pour les utilisateurs mentionnés.
Auteur uniquement
Employés du tenant
Visible dans CRM global
Avant lecture :
Visibility Tenant Permissions
edited = true editedAt = new Date()
Dans :
CustomerHistory
NOTE_CREATED NOTE_UPDATED NOTE_DELETED
deletedAt: new Date()
GET /customers/{id}/notes
noteType visibility author search
title content
contains: search
customer-note.controller.ts
@ApiTags( 'Customer Notes' ) @Controller( 'customers/:id/notes' ) @UseGuards( JwtAuthGuard ) @ApiBearerAuth()
GET /customers/{id}/notes
POST /customers/{id}/notes
PUT /customers/{id}/notes/{noteId}
DELETE /customers/{id}/notes/{noteId}
GET /notes/mentions
Mes mentions
Les notes alimentent :
Customer Timeline
future.
Notes Documents Tags Communications Réservations
VIP Risques Informations critiques
CUSTOMER_NOTE_CREATED CUSTOMER_NOTE_UPDATED CUSTOMER_NOTE_DELETED CUSTOMER_NOTE_PINNED CUSTOMER_MENTION_CREATED
customer.notes.read customer.notes.write customer.notes.delete customer.notes.manage
Compatible avec :
CustomerCommunication Campaign MarketingEvent
Compatible avec :
AiConversation Recommendation KnowledgeDocument
Le Sprint 3-F.1 est terminé lorsque :
✓ CustomerNote créé ✓ CustomerNoteMention créé ✓ Notes privées ✓ Notes publiques ✓ Mentions utilisateurs ✓ Historisation CRM ✓ AuditLog intégré ✓ Recherche notes ✓ Swagger documenté
CustomerNote CustomerNoteMention CustomerNoteModule CustomerNoteController CustomerNoteService CRM Notes API
Transformer le système de notes CRM en véritable plateforme collaborative Enterprise.
À l'issue de cette étape :
✓ Threads ✓ Réponses aux notes ✓ Réactions ✓ Tâches liées ✓ Assignation ✓ Notifications temps réel ✓ Timeline CRM unifiée ✓ Collaboration multi-équipes
Customer ↓ CRM Timeline ↓ Notes ↓ Threads ↓ Tasks ↓ Mentions ↓ Notifications ↓ Collaboration Hub
parentNoteId String?
threadRootId String?
replyCount Int
@default(0)
parentNote CustomerNote?
@relation(
"CustomerNoteThread",
fields:[parentNoteId],
references:[id]
)
replies CustomerNote[]
@relation(
"CustomerNoteThread"
)
model CustomerNoteReaction {
id String
@id
@default(uuid())
noteId String
userId String
reaction String
createdAt DateTime
@default(now())
note CustomerNote
@relation(
fields:[noteId],
references:[id],
onDelete:Cascade
)
user User
@relation(
fields:[userId],
references:[id]
)
@@unique([
noteId,
userId,
reaction
])
@@index([noteId])
@@index([userId])
}
model CustomerTask {
id String
@id
@default(uuid())
tenantId String
customerId String
noteId String?
assignedToId String?
createdById String
title String
description String?
priority TaskPriority
status TaskStatus
dueDate DateTime?
completedAt DateTime?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
customer Customer
@relation(
fields:[customerId],
references:[id]
)
@@index([tenantId])
@@index([customerId])
@@index([assignedToId])
@@index([status])
}
enum TaskPriority {
LOW
MEDIUM
HIGH
CRITICAL
}
enum TaskStatus {
OPEN
IN_PROGRESS
COMPLETED
CANCELLED
}
npx prisma migrate dev \
--name crm_collaboration
npx prisma generate
Dans :
CustomerNoteService
replyToNote()
Note ↓ Réponse ↓ Thread ↓ Timeline
Client VIP ↓ Note commerciale ↓ Réponse finance ↓ Réponse support
Conversation CRM unifiée
GET /customers/{id}/notes/{noteId}/thread
Arborescence complète
LIKE IMPORTANT FOLLOW_UP DONE WARNING
addReaction() removeReaction() listReactions()
POST /notes/{noteId}/reactions
DELETE /notes/{noteId}/reactions
assignedToId String?
assignedTo User?
PUT /notes/{noteId}/assign
Client réclame facture ↓ Assigné Finance
Notification Email In-App
customer-task.service.ts
createTask() assignTask() completeTask() cancelTask()
GET /customers/{id}/tasks
POST /customers/{id}/tasks
PUT /customers/{id}/tasks/{taskId}
DELETE /customers/{id}/tasks/{taskId}
Rappeler client ↓ Assigner commercial ↓ Date limite
Infrastructure :
Notification Gateway
prévue en Sprint 9.
NOTE_CREATED NOTE_REPLY NOTE_MENTION NOTE_ASSIGNED TASK_CREATED TASK_COMPLETED
tenant:{tenantId}
crm:{customerId}
crm-timeline.service.ts
CustomerNote CustomerDocument CustomerTag CustomerTask Reservation Invoice Communication
GET /customers/{id}/timeline
[
{
"type":"NOTE",
"date":"2026-01-01"
},
{
"type":"TASK",
"date":"2026-01-03"
},
{
"type":"DOCUMENT",
"date":"2026-01-05"
}
]
Notes Documents Tâches Tags Réservations Factures
GET /crm/collaboration/dashboard
Notes ouvertes Tâches ouvertes Mentions non lues Assignations Threads actifs
{
"openTasks":42,
"activeThreads":17,
"unreadMentions":9
}
NOTE_REPLIED NOTE_ASSIGNED TASK_CREATED TASK_COMPLETED
CRM_THREAD_CREATED CRM_NOTE_REACTION CRM_TASK_CREATED CRM_TASK_ASSIGNED CRM_TASK_COMPLETED
Campaign MarketingEvent NotificationCenter Realtime Communication
Compatible avec :
AiConversation Recommendation AutomationScenario KnowledgeDocument
Compatible avec :
Enterprise CRM Collaboration Analytics Customer Intelligence Unified Timeline
Le Sprint 3-F.2 est terminé lorsque :
✓ Threads CRM ✓ Réponses aux notes ✓ Réactions ✓ Assignation ✓ Tâches CRM ✓ Notifications temps réel ✓ Timeline unifiée ✓ Dashboard collaboration ✓ Audit intégré
CustomerNoteReaction CustomerTask CustomerTaskService CrmTimelineService CRM Collaboration API Unified CRM Timeline Realtime CRM Events
Implémenter la couche CRM Communication centralisée permettant de tracer toutes les interactions client.
À l'issue de cette étape :
✓ Emails ✓ SMS ✓ Appels ✓ Historique communications ✓ Timeline CRM ✓ Audit ✓ Statistiques ✓ Préparation Marketing
Customer ↓ CustomerCommunication ↓ Email SMS Calls ↓ Timeline CRM ↓ Analytics ↓ Marketing
model CustomerCommunication {
id String
@id
@default(uuid())
tenantId String
customerId String
userId String?
communicationType CommunicationType
direction CommunicationDirection
status CommunicationStatus
subject String?
content String?
externalReference String?
provider String?
sender String?
recipient String?
durationSeconds Int?
scheduledAt DateTime?
sentAt DateTime?
deliveredAt DateTime?
openedAt DateTime?
repliedAt DateTime?
metadata Json?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
deletedAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
user User?
@relation(
fields:[userId],
references:[id]
)
@@index([tenantId])
@@index([customerId])
@@index([communicationType])
@@index([status])
@@index([createdAt])
}
enum CommunicationType {
EMAIL
SMS
PHONE_CALL
WHATSAPP
PUSH_NOTIFICATION
LETTER
INTERNAL_NOTE
}
enum CommunicationDirection {
INBOUND
OUTBOUND
}
enum CommunicationStatus {
DRAFT
SCHEDULED
SENT
DELIVERED
OPENED
REPLIED
FAILED
CANCELLED
}
Dans :
model Customer
communications CustomerCommunication[]
npx prisma migrate dev \
--name customer_communications
npx prisma generate
src/modules/customer-communications ├── application │ │ └── dto │ │ ├── create-customer-communication.dto.ts │ │ ├── communication-query.dto.ts │ │ └── communication-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer-communication.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer-communication.controller.ts │ └── customer-communication.module.ts
create-customer-communication.dto.ts
export class CreateCustomerCommunicationDto {
communicationType: string;
direction: string;
subject?: string;
content?: string;
recipient?: string;
sender?: string;
scheduledAt?: Date;
}
export class CommunicationQueryDto {
communicationType?: string;
status?: string;
direction?: string;
page?: number = 1;
limit?: number = 25;
}
customer-communication.service.ts
findAll() findTimeline() create() markDelivered() markOpened() markReplied()
await prisma.customerCommunication.create({
data: {
tenantId,
customerId,
userId,
...dto
}
});
Dans :
CustomerHistory
COMMUNICATION_CREATED EMAIL_SENT SMS_SENT CALL_LOGGED
EmailService
SMTP SendGrid Mailgun AWS SES
CRM ↓ Email ↓ Send ↓ Tracking ↓ Timeline
sentAt deliveredAt openedAt repliedAt
SmsService
Twilio OVH SMS MessageBird
Sent Delivered Failed
PHONE_CALL
Duration Agent Outcome Recording
{
"communicationType":"PHONE_CALL",
"durationSeconds":420
}
GET /customers/{id}/communications/timeline
Communications + Notes + Documents + Tags
[
{
"type":"EMAIL",
"date":"2026-01-01"
},
{
"type":"SMS",
"date":"2026-01-02"
},
{
"type":"CALL",
"date":"2026-01-03"
}
]
customer-communication.controller.ts
@ApiTags( 'Customer Communications' ) @Controller( 'customers/:id/communications' ) @UseGuards( JwtAuthGuard )
GET /customers/{id}/communications
POST /customers/{id}/communications
GET /customers/{id}/communications/timeline
Filtres :
Type Statut Direction Période
COMMUNICATION_CREATED EMAIL_DELIVERED EMAIL_OPENED SMS_DELIVERED CALL_CREATED
GET /customers/{id}/communications/statistics
{
"emails":124,
"sms":31,
"calls":14,
"openRate":67,
"replyRate":19
}
communications.read communications.write communications.send communications.manage
Compatible avec :
Campaign EmailCampaign SmsCampaign NotificationCenter RealtimeMessaging
Compatible avec :
AiConversation Recommendation AutomationScenario KnowledgeBase
Compatible avec :
Customer Intelligence Communication Analytics Engagement Scoring Predictive CRM
Le Sprint 3-G.1 est terminé lorsque :
✓ CustomerCommunication créé ✓ Emails tracés ✓ SMS tracés ✓ Appels tracés ✓ Historique complet ✓ Timeline CRM ✓ AuditLog intégré ✓ Statistiques CRM ✓ Swagger documenté
CustomerCommunication CustomerCommunicationModule CustomerCommunicationController CustomerCommunicationService Communication Timeline Communication Analytics
Faire évoluer le module CRM Communication vers une plateforme omnicanale Enterprise.
À l'issue de cette étape :
✓ WhatsApp ✓ Messagerie instantanée ✓ Templates ✓ Campagnes ✓ Analyse de sentiment ✓ IA conversationnelle ✓ Centre omnicanal ✓ Communication Intelligence
Customer ↓ Omnichannel Hub ├── Email ├── SMS ├── WhatsApp ├── Chat ├── Push ├── Voice │ ↓ AI Intelligence ↓ Campaigns ↓ Analytics
model CommunicationTemplate {
id String
@id
@default(uuid())
tenantId String
name String
code String
channel CommunicationChannel
subject String?
content String
variables Json?
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@unique([
tenantId,
code
])
@@index([tenantId])
@@index([channel])
}
model OmnichannelConversation {
id String
@id
@default(uuid())
tenantId String
customerId String
channel CommunicationChannel
status ConversationStatus
subject String?
startedAt DateTime
@default(now())
lastMessageAt DateTime?
closedAt DateTime?
assignedToId String?
sentimentScore Float?
aiSummary String?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
customer Customer
@relation(
fields:[customerId],
references:[id]
)
@@index([tenantId])
@@index([customerId])
@@index([channel])
@@index([status])
}
model ConversationMessage {
id String
@id
@default(uuid())
conversationId String
direction CommunicationDirection
messageType String
content String?
externalReference String?
sentAt DateTime
@default(now())
metadata Json?
conversation OmnichannelConversation
@relation(
fields:[conversationId],
references:[id],
onDelete:Cascade
)
@@index([conversationId])
@@index([sentAt])
}
enum CommunicationChannel {
EMAIL
SMS
WHATSAPP
CHAT
PUSH
VOICE
}
enum ConversationStatus {
OPEN
ASSIGNED
WAITING_CUSTOMER
CLOSED
}
npx prisma migrate dev \
--name omnichannel_communications
src/modules/omnichannel
omnichannel ├── templates │ ├── conversations │ ├── whatsapp │ ├── chat │ ├── ai │ └── analytics
whatsapp.service.ts
sendMessage() sendTemplate() receiveWebhook() markDelivered()
Booking Confirmation Payment Reminder Welcome Message Review Request Check-In Reminder
POST /communications/whatsapp/send POST /communications/whatsapp/template POST /communications/whatsapp/webhook
chat.service.ts
startConversation() sendMessage() closeConversation() assignConversation()
WebSocket Gateway
CHAT_MESSAGE CHAT_TYPING CHAT_ASSIGNED CHAT_CLOSED
GET /conversations
GET /conversations/{id}
POST /conversations/{id}/messages
PUT /conversations/{id}/assign
communication-template.service.ts
{{customerName}}
{{reservationNumber}}
{{propertyName}}
{{checkInDate}}
{{amount}}
renderTemplate()
GET /communication-templates
POST /communication-templates
PUT /communication-templates/{id}
DELETE /communication-templates/{id}
model CampaignCommunication {
id String
@id
@default(uuid())
tenantId String
name String
channel CommunicationChannel
templateId String?
targetSegment String?
status String
scheduledAt DateTime?
sentAt DateTime?
recipientsCount Int
@default(0)
createdAt DateTime
@default(now())
@@index([tenantId])
@@index([status])
}
Newsletter Promotion Loyalty Upsell Cross-sell Retention
GET /campaigns
POST /campaigns
POST /campaigns/{id}/send
sentimentScore Float? aiSummary String?
sentiment-analysis.service.ts
NEGATIVE NEUTRAL POSITIVE
"Très satisfait" ↓ POSITIVE ↓ 0.92
conversation-ai.service.ts
Résumé automatique Détection intention Suggestions réponses Priorisation
Client demande remboursement ↓ Intent: REFUND_REQUEST ↓ Réponse suggérée
aiSummary
à la fermeture d'une conversation.
GET /omnichannel/dashboard
Conversations ouvertes Temps réponse moyen Messages aujourd'hui Sentiment moyen Campagnes actives
GET /omnichannel/inbox
Email SMS WhatsApp Chat
WHATSAPP_SENT CHAT_STARTED CHAT_ASSIGNED CAMPAIGN_CREATED CAMPAIGN_SENT SENTIMENT_ANALYZED AI_RESPONSE_GENERATED
communications.campaigns communications.templates communications.omnichannel communications.ai
Compatible avec :
NotificationCenter RealtimeMessaging CampaignEngine CommunicationHub
Compatible avec :
AiConversation AiMessage Recommendation AutomationScenario
Compatible avec :
Enterprise Analytics Communication Intelligence Customer Intelligence Revenue Intelligence
Le Sprint 3-G.2 est terminé lorsque :
✓ WhatsApp intégré ✓ Messagerie instantanée ✓ Templates ✓ Campagnes CRM ✓ Analyse de sentiment ✓ IA conversationnelle ✓ Centre omnicanal ✓ Dashboard omnicanal ✓ Audit intégré
CommunicationTemplate OmnichannelConversation ConversationMessage WhatsAppService ChatService ConversationAIService SentimentAnalysisService Omnichannel Dashboard
Implémenter la gestion centralisée des préférences client permettant :
Personnalisation Marketing Communication Fidélisation Consentements Expérience client
À l'issue de cette étape :
✓ Préférences marketing ✓ Canaux préférés ✓ Consentements ✓ Préférences linguistiques ✓ Préférences monétaires ✓ Personnalisation CRM ✓ Conformité RGPD
Customer ↓ CustomerPreference ├── Marketing ├── Communication ├── Language ├── Currency ├── Timezone ├── Loyalty └── GDPR ↓ CRM ↓ Marketing Automation
model CustomerPreference {
id String
@id
@default(uuid())
customerId String
@unique
preferredLanguage String?
preferredCurrency String?
preferredTimezone String?
preferredChannel PreferredChannel?
preferredContactTime String?
prefersEmail Boolean
@default(true)
prefersSms Boolean
@default(false)
prefersPhone Boolean
@default(false)
prefersWhatsApp Boolean
@default(false)
acceptsMarketing Boolean
@default(false)
acceptsNewsletter Boolean
@default(false)
acceptsPromotions Boolean
@default(false)
acceptsSurveys Boolean
@default(false)
acceptsRecommendations Boolean
@default(false)
loyaltyEligible Boolean
@default(true)
vipProgramEligible Boolean
@default(false)
communicationFrequency CommunicationFrequency?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
}
enum PreferredChannel {
EMAIL
SMS
PHONE
WHATSAPP
PUSH
}
enum CommunicationFrequency {
IMMEDIATE
DAILY
WEEKLY
MONTHLY
NEVER
}
marketingConsentAt DateTime? newsletterConsentAt DateTime? privacyAcceptedAt DateTime? consentVersion String? consentIpAddress String?
npx prisma migrate dev \
--name customer_preferences_enterprise
npx prisma generate
src/modules/customer-preferences ├── application │ │ └── dto │ │ ├── update-customer-preference.dto.ts │ │ └── customer-preference-response.dto.ts │ ├── domain │ │ └── services │ │ └── customer-preference.service.ts │ ├── presentation │ │ └── controllers │ │ └── customer-preference.controller.ts │ └── customer-preference.module.ts
update-customer-preference.dto.ts
export class UpdateCustomerPreferenceDto {
preferredLanguage?: string;
preferredCurrency?: string;
preferredTimezone?: string;
preferredChannel?: string;
preferredContactTime?: string;
prefersEmail?: boolean;
prefersSms?: boolean;
prefersPhone?: boolean;
prefersWhatsApp?: boolean;
acceptsMarketing?: boolean;
acceptsNewsletter?: boolean;
acceptsPromotions?: boolean;
acceptsSurveys?: boolean;
acceptsRecommendations?: boolean;
communicationFrequency?: string;
}
customer-preference-response.dto.ts
export class CustomerPreferenceResponseDto {
preferredLanguage: string;
preferredCurrency: string;
preferredChannel: string;
acceptsMarketing: boolean;
acceptsNewsletter: boolean;
communicationFrequency: string;
}
customer-preference.service.ts
findPreferences() updatePreferences() validatePreferences() recordConsent()
Customer Tenant
avant toute lecture.
CustomerPreference
await prisma.customerPreference.update({
where: {
customerId
},
data: {
...dto
}
});
acceptsMarketing=true
marketingConsentAt: new Date()
acceptsNewsletter=true
newsletterConsentAt: new Date()
Contre :
SupportedLanguages
Contre :
SupportedCurrencies
Contre :
IANA Timezones
PreferredChannel=SMS ↓ Téléphone requis
throw new BadRequestException()
Les préférences alimentent :
Marketing Communication Campagnes Recommandations Automatisation
Langue = FR ↓ Templates FR
Canal = WhatsApp ↓ Communication WhatsApp
Fréquence = Weekly ↓ Digest hebdomadaire
customer-preference.controller.ts
@ApiTags( 'Customer Preferences' ) @Controller( 'customers/:id/preferences' ) @UseGuards( JwtAuthGuard ) @ApiBearerAuth()
GET /customers/{id}/preferences
PUT /customers/{id}/preferences
PREFERENCES_UPDATED MARKETING_CONSENT_UPDATED COMMUNICATION_CHANNEL_CHANGED
CUSTOMER_PREFERENCES_UPDATED CUSTOMER_CONSENT_GRANTED CUSTOMER_CONSENT_REVOKED
GET /customers/preferences/statistics
{
"email":523,
"sms":121,
"whatsapp":84,
"marketingOptIn":432
}
acceptsMarketing=true AND preferredChannel=EMAIL
Campagnes CRM Marketing Automation
Compatible avec :
GDPR Export Data Portability
Retrait consentement Opt-out Désinscription
customer.preferences.read customer.preferences.write customer.consent.manage
Compatible avec :
Campaign MarketingSegment NotificationCenter CommunicationHub
Compatible avec :
Recommendation AiConversation AutomationScenario Customer Intelligence
Compatible avec :
Consent ComplianceAudit RetentionPolicy DataClassification
Le Sprint 3-H.1 est terminé lorsque :
✓ CustomerPreference enrichi ✓ CustomerPreferenceModule créé ✓ CustomerPreferenceController créé ✓ CustomerPreferenceService créé ✓ Consentements RGPD ✓ Personnalisation CRM ✓ Historisation ✓ AuditLog intégré ✓ Swagger documenté
CustomerPreference Enterprise CustomerPreferenceModule CustomerPreferenceController CustomerPreferenceService Consent Management Preference Analytics
Finaliser la couche CRM Enterprise de conformité et de gestion des consentements.
À l'issue de cette étape :
✓ Consentements RGPD avancés ✓ Historique complet ✓ Double Opt-In ✓ Centre omnicanal ✓ Gestion abonnements ✓ Portabilité des données ✓ Droit à l'oubli ✓ Audit de conformité
Customer ↓ Consent Center ├── Marketing ├── Newsletter ├── SMS ├── WhatsApp ├── Push ├── Profilage ├── Analytics └── Partage données ↓ Compliance ↓ GDPR
model Consent {
id String
@id
@default(uuid())
tenantId String
customerId String
consentType ConsentType
status ConsentStatus
source ConsentSource
version String
grantedAt DateTime?
revokedAt DateTime?
expiresAt DateTime?
ipAddress String?
userAgent String?
proofReference String?
metadata Json?
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
history ConsentHistory[]
@@index([tenantId])
@@index([customerId])
@@index([consentType])
@@index([status])
}
model ConsentHistory {
id String
@id
@default(uuid())
consentId String
oldStatus ConsentStatus?
newStatus ConsentStatus
changedAt DateTime
@default(now())
changedBy String?
reason String?
metadata Json?
consent Consent
@relation(
fields:[consentId],
references:[id],
onDelete:Cascade
)
@@index([consentId])
@@index([changedAt])
}
enum ConsentType {
MARKETING
NEWSLETTER
SMS
WHATSAPP
EMAIL
PROFILING
ANALYTICS
THIRD_PARTY_SHARING
PERSONALIZATION
}
enum ConsentStatus {
PENDING
GRANTED
REVOKED
EXPIRED
}
enum ConsentSource {
WEBSITE
CRM
API
IMPORT
MOBILE_APP
CUSTOMER_PORTAL
}
consents Consent[]
dans :
model Customer
npx prisma migrate dev \
--name consent_management
doubleOptInRequired Boolean
@default(false)
doubleOptInToken String?
doubleOptInSentAt DateTime?
doubleOptInConfirmedAt DateTime?
Inscription ↓ Consentement ↓ Email confirmation ↓ Lien sécurisé ↓ Consentement actif
double-optin.service.ts
generateToken() sendConfirmation() confirmConsent()
model CustomerSubscription {
id String
@id
@default(uuid())
customerId String
code String
name String
channel PreferredChannel
subscribed Boolean
@default(true)
subscribedAt DateTime?
unsubscribedAt DateTime?
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
@@index([customerId])
@@index([channel])
}
Newsletter Promotions Loyalty Product Updates Reservation Alerts Security Alerts
GET /customers/{id}/subscriptions
PUT /customers/{id}/subscriptions
consent-management.service.ts
grantConsent() revokeConsent() expireConsent() recordProof() getConsentHistory()
Toute modification crée :
ConsentHistory
Email SMS WhatsApp Campagne
Consent actif
gdpr-export.service.ts
Customer Preferences Consents Communications Documents Notes Tags Reservations
POST /customers/{id}/gdpr/export
JSON ZIP PDF
gdpr-erasure.service.ts
anonymizeCustomer() deletePersonalData() generateErasureReport()
Demande ↓ Validation ↓ Anonymisation ↓ Rapport ↓ Audit
POST /customers/{id}/gdpr/erase
Email Téléphone Adresse Documents Métadonnées personnelles
GET /preferences-center/{token}
PUT /preferences-center/{token}
Modifier préférences Modifier abonnements Retirer consentements Télécharger données
JWT signé Expiration configurable
GET /compliance/consents/dashboard
Total Consents Active Consents Revoked Consents Double Opt-In Pending Export Requests Erasure Requests
{
"activeConsents":8421,
"revokedConsents":213,
"pendingDoubleOptIn":54
}
CONSENT_GRANTED CONSENT_REVOKED DOUBLE_OPTIN_CONFIRMED GDPR_EXPORT_REQUESTED GDPR_ERASURE_REQUESTED GDPR_ERASURE_COMPLETED
Compatible avec :
Sprint 19 ISO27001 SOC2 GDPR Audit
Compatible avec :
RetentionPolicy DataClassification SecurityIncident
consents.read consents.write consents.manage gdpr.export gdpr.erase
Compatible avec :
Campaign NotificationCenter CommunicationHub Marketing Automation
Compatible avec :
Consent ComplianceAudit RetentionPolicy DataClassification SecurityPolicy
Compatible avec :
Enterprise Governance Enterprise Compliance Privacy Center Customer Trust Dashboard
Le Sprint 3-H.2 est terminé lorsque :
✓ Consent créé ✓ ConsentHistory créé ✓ Double Opt-In ✓ Centre omnicanal ✓ Gestion abonnements ✓ Portabilité données ✓ Droit à l'oubli ✓ Dashboard conformité ✓ Audit intégré
Consent ConsentHistory CustomerSubscription ConsentManagementService DoubleOptInService GdprExportService GdprErasureService Compliance Dashboard
Mettre en place un moteur de recherche CRM Enterprise capable de rechercher instantanément dans l'ensemble du domaine Client.
À l'issue de cette étape :
✓ Recherche globale ✓ Recherche plein texte ✓ Recherche multicritères ✓ Filtres avancés ✓ Facettes ✓ Recherches sauvegardées ✓ CRM Search Engine
CRM Search API ↓ Search Engine ├── Customers ├── Notes ├── Documents ├── Communications ├── Tags ├── Preferences └── Reservations ↓ Filters Engine ↓ Facets Engine ↓ Saved Searches
model SavedSearch {
id String
@id
@default(uuid())
tenantId String
userId String
name String
description String?
entityType SearchEntityType
query String?
filters Json
isDefault Boolean
@default(false)
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 SearchHistory {
id String
@id
@default(uuid())
tenantId String
userId String
query String
entityType SearchEntityType?
resultCount Int
@default(0)
executedAt DateTime
@default(now())
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
user User
@relation(
fields:[userId],
references:[id]
)
@@index([tenantId])
@@index([userId])
@@index([executedAt])
}
enum SearchEntityType {
CUSTOMER
NOTE
DOCUMENT
COMMUNICATION
TAG
RESERVATION
GLOBAL
}
npx prisma migrate dev \
--name crm_search_engine
src/modules/crm-search ├── application │ │ └── dto │ │ ├── crm-search.dto.ts │ │ ├── facet-filter.dto.ts │ │ └── saved-search.dto.ts │ ├── domain │ │ └── services │ │ ├── crm-search.service.ts │ │ ├── crm-facet.service.ts │ │ └── saved-search.service.ts │ ├── presentation │ │ └── controllers │ │ └── crm-search.controller.ts │ └── crm-search.module.ts
crm-search.dto.ts
export class CrmSearchDto {
q?: string;
entityType?: string;
page?: number = 1;
limit?: number = 25;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
filters?: Record<
string,
unknown
>;
}
Status Segment Tags Score Revenue Risk Country Language Consent Date
crm-search.service.ts
globalSearch() searchCustomers() searchNotes() searchDocuments() searchCommunications()
CustomerNumber FirstName LastName Email Phone CompanyName
OR: [
{
firstName: {
contains: q,
mode: 'insensitive'
}
},
{
lastName: {
contains: q,
mode: 'insensitive'
}
},
{
email: {
contains: q,
mode: 'insensitive'
}
}
]
Customers Notes Documents Communications
{
"customers": [],
"notes": [],
"documents": [],
"communications": []
}
Colonnes :
title content subject
tsvector tsquery
CREATE INDEX idx_note_search ON "CustomerNote" USING GIN( to_tsvector( 'simple', content ) );
VIP facture
Notes
Documents
Emails concernés.
VIP + France + Revenue > 5000 + Marketing = true
AND: [ ... ]
Equals Contains In Range Date Boolean
crm-facet.service.ts
buildFacets() countSegments() countTags() countCountries()
{
"segments": {
"VIP": 41,
"STANDARD": 381
},
"countries": {
"FR": 250,
"ES": 71
}
}
GET /crm-search/facets
saved-search.service.ts
createSearch() listSearches() deleteSearch() executeSavedSearch()
VIP France
Mes VIP France
GET /crm-search/saved
POST /crm-search/saved
DELETE /crm-search/saved/{id}
crm-search.controller.ts
GET /crm-search
GET /crm-search/global
GET /crm-search/facets
GET /crm-search/saved
POST /crm-search/saved
DELETE /crm-search/saved/{id}
GET /crm-search/global?q=dupont
{
"customers": 3,
"notes": 5,
"communications": 2
}
SearchHistory
automatiquement.
CRM_SEARCH CRM_GLOBAL_SEARCH CRM_SAVED_SEARCH CRM_SEARCH_EXPORT
GET /crm-search/statistics
{
"searchesToday":421,
"savedSearches":82,
"mostUsedFilter":"VIP"
}
Compatible avec :
Recommendation KnowledgeDocument AiConversation AutomationScenario
Compatible avec :
RevenueForecast CustomerAnalytics Predictive Segmentation
Compatible avec :
Enterprise Search Global Search Data Intelligence Enterprise Analytics
Le Sprint 3-I.1 est terminé lorsque :
✓ Recherche globale ✓ Recherche plein texte ✓ Recherche multicritères ✓ Facettes CRM ✓ Recherches sauvegardées ✓ Historique recherches ✓ Audit intégré ✓ Swagger documenté
SavedSearch SearchHistory CrmSearchService CrmFacetService SavedSearchService CRM Search API Enterprise Search Engine
Faire évoluer le moteur CRM Search vers une plateforme de recherche intelligente assistée par IA.
À l'issue de cette étape :
✓ Recherche sémantique ✓ Suggestions intelligentes ✓ Recherche IA ✓ Résultats personnalisés ✓ Recherche prédictive ✓ Recommandations CRM ✓ Search Intelligence Engine
User Query ↓ Search Intelligence Layer ├── Full Text Search ├── Semantic Search ├── Predictive Search ├── Personalized Ranking └── AI Recommendations ↓ CRM Knowledge Graph ↓ Results Engine ↓ Recommendations
model SearchSuggestion {
id String
@id
@default(uuid())
tenantId String
query String
frequency Int
@default(1)
lastUsedAt DateTime
@default(now())
createdAt DateTime
@default(now())
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@unique([
tenantId,
query
])
@@index([tenantId])
@@index([frequency])
@@index([lastUsedAt])
}
model SearchRecommendation {
id String
@id
@default(uuid())
tenantId String
userId String
recommendationType RecommendationType
entityType SearchEntityType
entityId String
score Float
reason String?
createdAt DateTime
@default(now())
expiresAt DateTime?
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
user User
@relation(
fields:[userId],
references:[id]
)
@@index([tenantId])
@@index([userId])
@@index([score])
}
enum RecommendationType {
RECENT
FREQUENT
RELATED
PREDICTIVE
AI_GENERATED
BEHAVIOR_BASED
}
npx prisma migrate dev \
--name crm_search_ai
src/modules/search-intelligence ├── semantic-search │ ├── suggestions │ ├── recommendations │ ├── predictive-search │ ├── ranking │ └── ai-search
semantic-search.service.ts
semanticSearch() findRelatedTerms() expandQuery()
VIP = Premium Client = Customer Facture = Invoice Réservation = Booking
Recherche : "client premium"
VIP Premium High Value Loyal Customer
search-suggestion.service.ts
generateSuggestions() recordSearch() getPopularQueries()
Historique utilisateur Historique tenant Popularité Fréquence
GET /crm-search/suggestions
vip vip france vip paris vip corporate
personalized-ranking.service.ts
rankResults() applyUserPreferences() boostFrequentlyUsedEntities()
Historique utilisateur Segments favoris Clients récents Résultats utilisés
VIP Leads Réservations
plus visibles.
Factures Paiements Contrats
plus visibles.
predictive-search.service.ts
predictNextQueries() predictCustomerInterest() predictRelatedSearches()
VIP France
VIP Paris VIP Revenue > 10000 VIP Loyalty
SearchHistory SavedSearch CustomerActivity
search-recommendation.service.ts
generateRecommendations() recommendCustomers() recommendSegments() recommendActions()
23 clients à risque non contactés
12 VIP sans campagne active
GET /crm-search/recommendations
ai-search.service.ts
naturalLanguageSearch() explainResults() generateInsights()
Montre-moi les clients VIP qui n'ont pas été contactés depuis 90 jours.
Segment=VIP AND LastCommunication < 90 jours
POST /crm-search/ai
GET /crm-search/intelligence/dashboard
Top recherches Suggestions utilisées Recherche IA Taux de clic Recommandations générées
{
"topSearch":"VIP",
"aiSearches":128,
"recommendations":342
}
SEMANTIC_SEARCH AI_SEARCH SEARCH_RECOMMENDATION SEARCH_SUGGESTION_SELECTED PREDICTIVE_SEARCH
crm.search.ai crm.search.recommendations crm.search.analytics
Compatible avec :
AiConversation KnowledgeGraph RecommendationEngine AutomationScenario
Compatible avec :
RevenueForecast CustomerAnalytics PredictiveCRM BusinessIntelligence
Compatible avec :
Enterprise AI Search Intelligence Enterprise Knowledge Decision Support System
Le Sprint 3-I.2 est terminé lorsque :
✓ Recherche sémantique ✓ Suggestions intelligentes ✓ Recherche IA ✓ Résultats personnalisés ✓ Recherche prédictive ✓ Recommandations CRM ✓ Dashboard Search Intelligence ✓ Audit intégré
SearchSuggestion SearchRecommendation SemanticSearchService PredictiveSearchService AISearchService SearchRecommendationService Search Intelligence Dashboard
Construire le premier cockpit CRM Enterprise centralisant l'ensemble des indicateurs stratégiques.
À l'issue de cette étape :
✓ KPI CRM ✓ Customer Dashboard ✓ Segmentation Analytics ✓ Communication Analytics ✓ Conversion Analytics ✓ Revenue Analytics ✓ Executive Dashboard
CRM Analytics ↓ Analytics Engine ├── Customers ├── Segments ├── Communications ├── Revenue ├── Conversion └── Loyalty ↓ Dashboard API ↓ Frontend Cockpit
model DashboardSnapshot {
id String
@id
@default(uuid())
tenantId String
snapshotDate DateTime
metrics Json
createdAt DateTime
@default(now())
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@unique([
tenantId,
snapshotDate
])
@@index([tenantId])
@@index([snapshotDate])
}
model KpiDefinition {
id String
@id
@default(uuid())
tenantId String
code String
name String
description String?
category String
active Boolean
@default(true)
createdAt DateTime
@default(now())
updatedAt DateTime
@updatedAt
@@unique([
tenantId,
code
])
@@index([tenantId])
}
npx prisma migrate dev \
--name crm_analytics_dashboard
npx prisma generate
src/modules/crm-analytics ├── application │ │ └── dto │ ├── domain │ │ └── services │ │ ├── crm-dashboard.service.ts │ │ ├── customer-analytics.service.ts │ │ ├── communication-analytics.service.ts │ │ ├── segmentation-analytics.service.ts │ │ ├── conversion-analytics.service.ts │ │ └── revenue-analytics.service.ts │ ├── presentation │ │ └── controllers │ │ └── crm-analytics.controller.ts │ └── crm-analytics.module.ts
Nombre clients Clients actifs Clients inactifs VIP Nouveaux clients Clients à risque
{
"totalCustomers": 12584,
"activeCustomers": 10874,
"inactiveCustomers": 1710,
"vipCustomers": 524
}
customer-analytics.service.ts
getCustomerKpis() getGrowthRate() getCustomerHealth()
GET /crm-analytics/dashboard
{
"customers": {},
"communications": {},
"revenue": {},
"segments": {}
}
Top clients Derniers clients Clients à risque Clients VIP Croissance
{
"topCustomers": [],
"vipCustomers": [],
"riskCustomers": []
}
segmentation-analytics.service.ts
getSegmentDistribution() getSegmentGrowth() getSegmentRevenue()
VIP Corporate Loyal High Value Standard At Risk
GET /crm-analytics/segments
{
"VIP": 412,
"LOYAL": 1832,
"CORPORATE": 94
}
communication-analytics.service.ts
getCommunicationStats() getOpenRate() getReplyRate() getChannelPerformance()
Emails envoyés Emails ouverts SMS envoyés SMS délivrés WhatsApp envoyés Réponses
GET /crm-analytics/communications
{
"emailOpenRate": 68.4,
"replyRate": 17.2,
"smsDeliveryRate": 95.8
}
conversion-analytics.service.ts
getConversionRate() getLeadConversion() getCampaignConversion()
Client créé ↓ Premier achat ↓ Client fidèle
GET /crm-analytics/conversions
{
"leadConversion": 22.3,
"repeatCustomerRate": 38.1
}
revenue-analytics.service.ts
getRevenueMetrics() getLifetimeValue() getAverageBasket() getRevenueBySegment()
Revenue total MRR CRM Lifetime Value Average Basket Revenue Segment
GET /crm-analytics/revenue
{
"revenue": 1542870,
"averageBasket": 382,
"averageLtv": 4120
}
GET /crm-analytics/executive
Croissance clients Croissance revenu VIP Attrition Satisfaction Engagement
Tous les jours :
DashboardSnapshot
Historique Tendances Comparaisons
{
"customersGrowth": 12.4,
"revenueGrowth": 18.7
}
CRM_DASHBOARD_VIEW CRM_ANALYTICS_VIEW CRM_REVENUE_ANALYTICS CRM_SEGMENT_ANALYTICS CRM_EXPORT_ANALYTICS
crm.analytics.read crm.analytics.export crm.analytics.executive
GET /crm-analytics/export
CSV XLSX PDF
Compatible avec :
RecommendationEngine AiInsights Forecasting Business Intelligence
Compatible avec :
Revenue Forecast Customer Forecast Predictive Analytics Financial Analytics
Compatible avec :
Executive Cockpit Enterprise BI Enterprise Analytics Data Warehouse
Le Sprint 3-J.1 est terminé lorsque :
✓ KPI CRM ✓ Customer Dashboard ✓ Segmentation Analytics ✓ Communication Analytics ✓ Conversion Analytics ✓ Revenue Analytics ✓ Dashboard exécutif ✓ Export Analytics ✓ Audit intégré
DashboardSnapshot KpiDefinition CrmDashboardService CustomerAnalyticsService CommunicationAnalyticsService ConversionAnalyticsService RevenueAnalyticsService Executive Dashboard
Finaliser le CRM Enterprise en ajoutant une couche complète d'intelligence prédictive.
À l'issue de cette étape :
✓ Prévision revenus ✓ Prévision churn ✓ Prévision LTV ✓ Détection opportunités ✓ Détection anomalies ✓ Insights IA ✓ Score santé client ✓ CRM Intelligence Platform
CRM Data ↓ Analytics Engine ↓ Prediction Engine ├── Revenue Forecast ├── Churn Prediction ├── LTV Prediction ├── Opportunity Detection ├── Anomaly Detection └── Customer Health ↓ AI Insights ↓ Executive Dashboard
model CustomerHealth {
id String
@id
@default(uuid())
customerId String
@unique
overallScore Int
@default(0)
engagementScore Int
@default(0)
loyaltyScore Int
@default(0)
satisfactionScore Int
@default(0)
communicationScore Int
@default(0)
revenueScore Int
@default(0)
churnRiskScore Int
@default(0)
lastCalculatedAt DateTime
@default(now())
customer Customer
@relation(
fields:[customerId],
references:[id],
onDelete:Cascade
)
}
model PredictionSnapshot {
id String
@id
@default(uuid())
tenantId String
predictionType PredictionType
snapshotDate DateTime
result Json
createdAt DateTime
@default(now())
tenant Tenant
@relation(
fields:[tenantId],
references:[id]
)
@@index([tenantId])
@@index([predictionType])
@@index([snapshotDate])
}
model AiInsight {
id String
@id
@default(uuid())
tenantId String
category InsightCategory
severity InsightSeverity
title String
description String
recommendation String?
entityType String?
entityId String?
active Boolean
@default(true)
createdAt DateTime
@default(now())
expiresAt DateTime?
@@index([tenantId])
@@index([category])
@@index([severity])
@@index([active])
}
enum PredictionType {
REVENUE
CHURN
LTV
OPPORTUNITY
CUSTOMER_HEALTH
}
enum InsightCategory {
REVENUE
CHURN
CUSTOMER
SEGMENT
COMMUNICATION
MARKETING
}
enum InsightSeverity {
LOW
MEDIUM
HIGH
CRITICAL
}
npx prisma migrate dev \
--name crm_predictive_intelligence
src/modules/crm-intelligence ├── revenue-forecast │ ├── churn-prediction │ ├── ltv-prediction │ ├── opportunity-detection │ ├── anomaly-detection │ ├── ai-insights │ ├── customer-health │ └── dashboard
revenue-forecast.service.ts
forecastRevenue() forecastSegmentRevenue() forecastCustomerRevenue()
Invoices Reservations Customer LTV Customer Score Segments
GET /crm-intelligence/revenue-forecast
{
"nextMonth": 152000,
"nextQuarter": 487000,
"confidence": 0.84
}
churn-prediction.service.ts
calculateChurnRisk() predictChurn() detectInactiveCustomers()
Dernière activité Dernière communication Baisse dépenses Baisse engagement Réclamations
LOW MEDIUM HIGH CRITICAL
GET /crm-intelligence/churn
{
"highRiskCustomers": 42,
"criticalCustomers": 8
}
ltv-prediction.service.ts
predictLifetimeValue() predictVipCustomers() estimateFutureRevenue()
Historique achats Segments Fidélité Engagement Communication
GET /crm-intelligence/ltv
{
"averagePredictedLtv": 5420,
"futureVipCustomers": 73
}
opportunity-detection.service.ts
findUpsellOpportunities() findCrossSellOpportunities() findRetentionActions()
VIP sans offre premium ↓ Upsell
Corporate sans programme fidélité ↓ Cross-sell
GET /crm-intelligence/opportunities
anomaly-detection.service.ts
detectRevenueAnomalies() detectBehaviorAnomalies() detectCommunicationAnomalies()
Baisse revenu -45% ↓ Anomalie
Ouvertures emails 0% ↓ Alerte
GET /crm-intelligence/anomalies
customer-health.service.ts
calculateHealthScore() updateHealthScores() getHealthDistribution()
Engagement 20% Communication 15% Loyalty 20% Revenue 20% Satisfaction 15% Churn Risk 10%
90-100 Excellent 75-89 Healthy 50-74 Attention 0-49 Critical
GET /crm-intelligence/health
ai-insight.service.ts
generateInsights() generateRecommendations() prioritizeActions()
23 VIP sans contact depuis 60 jours
Créer campagne VIP
Segment Corporate +18% revenu ce mois-ci
GET /crm-intelligence/insights
GET /crm-intelligence/dashboard
Forecast Revenue Churn LTV Health Opportunities Insights
{
"forecastRevenue": 487000,
"highRiskCustomers": 42,
"futureVipCustomers": 73,
"opportunities": 118,
"insights": 14
}
CRM_FORECAST_GENERATED CRM_CHURN_ANALYSIS CRM_LTV_PREDICTION CRM_ANOMALY_DETECTED CRM_AI_INSIGHT_GENERATED
crm.intelligence.read crm.intelligence.forecast crm.intelligence.insights crm.intelligence.executive
Compatible avec :
Forecast Engine Revenue Planning Financial Intelligence Business Intelligence
Compatible avec :
Enterprise AI Executive Intelligence Decision Support Predictive Analytics Enterprise BI
Le Sprint 3-J.2 est terminé lorsque :
✓ Revenue Forecast ✓ Churn Prediction ✓ LTV Prediction ✓ Opportunity Detection ✓ Anomaly Detection ✓ Customer Health Score ✓ AI Insights ✓ CRM Intelligence Dashboard ✓ Audit intégré
CustomerHealth PredictionSnapshot AiInsight RevenueForecastService ChurnPredictionService LtvPredictionService OpportunityDetectionService AnomalyDetectionService CustomerHealthService AiInsightService CRM Intelligence Dashboard
✓ Customer ✓ CustomerAddress ✓ CustomerDocument ✓ CustomerTag ✓ CustomerNote ✓ CustomerCommunication ✓ CustomerPreference ✓ Consent Management ✓ CRM Search ✓ CRM Analytics ✓ CRM Intelligence