Save France
Backend

Structure des données

Schéma de la base de données PostgreSQL et entités Doctrine

Introduction

Cette documentation décrit la structure complète de la base de données PostgreSQL utilisée par l'application. Le schéma est géré via Doctrine ORM et comprend les entités pour la gestion des demandes de maintenance, des dépannages, des équipements, des offres et des contrats.

Vue d'ensemble

La base de données est organisée en plusieurs domaines métier :

  • Maintenance : Demandes de maintenance, offres, équipements, contrats, prix, réductions
  • Troubleshooting : Demandes de dépannage avec diagnostics
  • Equipment : Catégories, types et marques d'équipements
  • MaintenanceOffer : Offres de maintenance avec caractéristiques
  • Référentiels : Civilités, codes postaux, paramètres, raisons de refus
  • Authentification : Admins, tokens de rafraîchissement, réinitialisation de mot de passe

Schéma de la base de données

erDiagram
    ADMIN ||--o{ RESET_PASSWORD_REQUEST : has
    ADMIN ||--o{ REFRESH_TOKEN : has
    
    REQUEST ||--|| OFFER : has
    REQUEST ||--|| ADDRESS_CONTACT : has
    REQUEST ||--o| ADDRESS_BILLING : has
    REQUEST ||--o{ EQUIPMENT : contains
    REQUEST ||--o{ CONTRACT : generates
    REQUEST ||--o| REFUSAL : may_have
    REQUEST }o--|| DISCOUNT : may_have
    
    OFFER ||--|| PRICE : has
    OFFER }o--|| MAINTENANCE_OFFER : references
    
    MAINTENANCE_OFFER ||--o{ FEATURE : has
    
    EQUIPMENT }o--|| BRAND : references
    EQUIPMENT }o--|| TYPE : references
    
    TYPE }o--|| CATEGORY : belongs_to
    CATEGORY ||--o{ COEFFICIENT_RATE : has
    
    CONTRACT }o--|| REQUEST : belongs_to
    
    REFUSAL ||--o{ REFUSAL_REASON : has
    REFUSAL }o--|| REQUEST : belongs_to
    
    ADDRESS }o--|| CIVILITY : references
    
    TROUBLESHOOTING ||--|| ADDRESS : has
    TROUBLESHOOTING ||--o{ DIAGNOSIS : has
    
    ADMIN {
        int id PK
        string email UK
        array roles
        string password
    }
    
    REQUEST {
        uuid id PK
        datetime_immutable createdAt
        datetime_immutable updatedAt
        datetime_immutable expiredAt
        string status
        string paymentId
        string paymentStatus
        string paymentUrl
        boolean lessThanTwoYears
        datetime_immutable deletedAt
        datetime_immutable archivedAt
    }
    
    OFFER {
        uuid id PK
        uuid request_id FK
        int maintenanceOffer_id FK
        string paymentType
    }
    
    PRICE {
        int id PK
        uuid offer_id FK
        int ht
        int htBase
        int ttc
        int ttcBase
        int htTotal
        int htTotalBase
        int ttcTotal
        int ttcTotalBase
        boolean discount
        array details
    }
    
    EQUIPMENT {
        int id PK
        uuid request_id FK
        int brand_id FK
        int type_id FK
        date_immutable installedAt
        int uiDuctable
        int uiNotDuctable
    }
    
    CONTRACT {
        uuid id PK
        uuid request_id FK
        string path
        string signId
        string signUrl
        string status
        datetime_immutable expiredAt
        datetime_immutable createdAt
        string savId
    }
    
    MAINTENANCE_OFFER {
        int id PK
        string name
        string savLabel
        int subscriptionDuration
        int subscriptionVisit
        text description
        int loyaltyDiscount
        float coefficient
        string type
        boolean best
        int position
    }
    
    FEATURE {
        int id PK
        string label
    }
    
    CATEGORY {
        int id PK
        string name
        string savLabel
    }
    
    TYPE {
        int id PK
        int category_id FK
        string name
        string savLabel
        string section
        float hourlyPrice
        float hourRequire
        float supplyPrice
        float individualVAT
        float professionalVAT
    }
    
    BRAND {
        int id PK
        string name
        string savLabel
    }
    
    COEFFICIENT_RATE {
        int id PK
        int category_id FK
        int minEquipment
        float coefficient
    }
    
    ADDRESS {
        int id PK
        int civility_id FK
        string name
        string lastname
        boolean isPro
        string companyName
        string siret
        string vatNumber
        string street
        string additionalInfo
        string postalCode
        string city
        string email
        string phone
    }
    
    DISCOUNT {
        int id PK
        uuid request_id FK
        string name
        string code UK
        string type
        int amount
        boolean enabled
        datetime_immutable startAt
        datetime_immutable endAt
    }
    
    REFUSAL {
        uuid id PK
        uuid request_id FK
        text comment
        datetime_immutable createdAt
    }
    
    REFUSAL_REASON {
        int id PK
        string name
    }
    
    TROUBLESHOOTING {
        uuid id PK
        int address_id FK
        boolean hasContract
        text details
        array availability
        text availabilityDetails
        string status
        text comments
        datetime_immutable createdAt
        datetime_immutable updatedAt
    }
    
    DIAGNOSIS {
        int id PK
        string name
    }
    
    CIVILITY {
        int id PK
        string name
        string savLabel
    }
    
    POSTAL_CODE {
        int id PK
        string code UK
        string label
        float price
        float diagnosisPrice
    }
    
    PARAMETER {
        int id PK
        string code UK
        string label
        string value
    }

Tables principales

Maintenance

request (Maintenance\Request)

Table principale pour les demandes de maintenance.

ColonneTypeContraintesDescription
iduuidPK, NOT NULLIdentifiant unique (UUID v6)
created_atdatetime_immutableNOT NULLDate de création
updated_atdatetime_immutableNOT NULLDate de mise à jour
expired_atdatetime_immutableNOT NULLDate d'expiration (4 mois après création)
statusenumNOT NULLStatut de la demande (PENDING, WAITING_SIGNATURE, WAITING_PAYMENT, COMPLETE, REJECTED)
payment_idvarchar(255)NULLID de paiement Stripe
payment_statusenumNULLStatut du paiement
payment_urltextNULLURL de paiement Stripe
less_than_two_yearsbooleanNOT NULL, DEFAULT falseÉquipement de moins de 2 ans
deleted_atdatetime_immutableNULLDate de suppression (soft delete)
archived_atdatetime_immutableNULLDate d'archivage

Relations :

  • OneToOneoffer (Maintenance\Offer)
  • OneToOnecontact (Address)
  • OneToOnebilling_info (Address)
  • OneToManyequipments (Maintenance\Equipment)
  • OneToManycontracts (Maintenance\Contract)
  • OneToOnerefusal (Maintenance\Refusal)
  • ManyToOnediscount (Maintenance\Discount)

request_offer (Maintenance\Offer)

Offres associées aux demandes.

ColonneTypeContraintesDescription
iduuidPK, NOT NULLIdentifiant unique
request_iduuidFK, NOT NULL, UNIQUERéférence à request
maintenance_offer_idintFK, NOT NULLRéférence à maintenance_offer
payment_typeenumNOT NULLType de paiement (ONE_SHOT, SUBSCRIPTION)

Relations :

  • ManyToOnerequest (Maintenance\Request)
  • ManyToOnemaintenanceOffer (MaintenanceOffer\MaintenanceOffer)
  • OneToOneprice (Maintenance\Price)

request_equipment (Maintenance\Equipment)

Équipements associés à une demande.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
request_iduuidFK, NOT NULLRéférence à request
brand_idintFK, NOT NULLRéférence à equipment_brand
type_idintFK, NOT NULLRéférence à equipment_type
installed_atdate_immutableNULLDate d'installation
ui_ductableintNOT NULL, DEFAULT 0Nombre d'unités intérieures gainables
ui_not_ductableintNOT NULL, DEFAULT 0Nombre d'unités intérieures non gainables

Relations :

  • ManyToOnerequest (Maintenance\Request)
  • ManyToOnebrand (Equipment\Brand)
  • ManyToOnetype (Equipment\Type)

request_contract (Maintenance\Contract)

Contrats générés pour les demandes.

ColonneTypeContraintesDescription
iduuidPK, NOT NULLIdentifiant unique
request_iduuidFK, NOT NULLRéférence à request
pathvarchar(255)NOT NULLChemin vers le PDF du contrat
sign_idvarchar(255)NULLID de signature Yousign
sign_urltextNULLURL de signature Yousign
statusenumNOT NULLStatut (PENDING, SIGNED, EXPIRED, CANCELLED)
expired_atdatetime_immutableNOT NULLDate d'expiration (90 jours après création)
created_atdatetime_immutableNOT NULLDate de création
sav_idvarchar(255)NULLID dans le système SAV

Relations :

  • ManyToOnerequest (Maintenance\Request)

price (Maintenance\Price)

Prix calculés pour les offres.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
offer_iduuidFK, NOT NULL, UNIQUERéférence à request_offer
htintNOT NULLPrix HT mensuel (en centimes)
ht_baseintNULLPrix HT de base (avant réduction)
ttcintNOT NULLPrix TTC mensuel (en centimes)
ttc_baseintNULLPrix TTC de base (avant réduction)
ht_totalintNOT NULLPrix HT total (en centimes)
ht_total_baseintNULLPrix HT total de base
ttc_totalintNOT NULLPrix TTC total (en centimes)
ttc_total_baseintNULLPrix TTC total de base
discountbooleanNOT NULL, DEFAULT falseIndique si une réduction est appliquée
detailsjsonNULLDétails du calcul du prix

Relations :

  • OneToOneoffer (Maintenance\Offer)

discount (Maintenance\Discount)

Codes de réduction.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom de la réduction
codevarchar(255)NOT NULL, UNIQUECode de réduction
typevarchar(255)NOT NULLType de réduction (percentage, fixed)
amountintNOT NULLMontant de la réduction
enabledbooleanNOT NULLActif ou non
start_atdatetime_immutableNOT NULLDate de début
end_atdatetime_immutableNOT NULLDate de fin

Relations :

  • OneToManyrequests (Maintenance\Request)

request_refusal (Maintenance\Refusal)

Refus de demandes.

ColonneTypeContraintesDescription
iduuidPK, NOT NULLIdentifiant unique
request_iduuidFK, NOT NULL, UNIQUERéférence à request
commenttextNULLCommentaire de refus
created_atdatetime_immutableNOT NULLDate de création

Relations :

  • OneToOnerequest (Maintenance\Request)
  • ManyToManyreasons (RefusalReason) via table request_refusal_reason

Equipment

equipment_category (Equipment\Category)

Catégories d'équipements.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom de la catégorie
sav_labelvarchar(255)NOT NULLLabel pour le SAV

Relations :

  • OneToManyequipmentTypes (Equipment\Type)
  • OneToManycoefficientRates (CoefficientRate)

equipment_type (Equipment\Type)

Types d'équipements.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
category_idintFK, NOT NULLRéférence à equipment_category
namevarchar(255)NOT NULLNom du type
sav_labelvarchar(255)NOT NULLLabel pour le SAV
sectionstring (enum)NULLSection d'affichage front (pac_air_air, pac_air_eau). Permet de regrouper plusieurs catégories (ex. PAC Air/Air hors gainable + Gainable) dans une même section.
hourly_pricefloatNOT NULLPrix horaire
hour_requirefloatNOT NULLHeures requises
supply_pricefloatNOT NULLPrix des fournitures
individual_vatfloatNOT NULLTVA pour particulier
professional_vatfloatNOT NULLTVA pour professionnel

Relations :

  • ManyToOnecategory (Equipment\Category)

equipment_brand (Equipment\Brand)

Marques d'équipements.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom de la marque
sav_labelvarchar(255)NOT NULLLabel pour le SAV

MaintenanceOffer

maintenance_offer (MaintenanceOffer\MaintenanceOffer)

Offres de maintenance disponibles.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom de l'offre
sav_labelvarchar(255)NULLLabel pour le SAV
subscription_durationintNOT NULLDurée d'engagement (en mois)
subscription_visitintNOT NULLNombre de visites incluses
descriptiontextNULLDescription de l'offre
loyalty_discountintNULLRéduction fidélité (en %)
coefficientfloatNULLCoefficient de calcul
typeenumNOT NULLType (RECURRENT, PUNCTUAL)
bestbooleanNOT NULL, DEFAULT falseOffre recommandée
positionintNOT NULLPosition d'affichage

Relations :

  • ManyToManyfeatures (MaintenanceOffer\Feature) via table maintenance_offer_feature

feature (MaintenanceOffer\Feature)

Caractéristiques des offres.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
labelvarchar(255)NOT NULLLibellé de la caractéristique

Troubleshooting

troubleshooting (Troubleshooting\Troubleshooting)

Demandes de dépannage.

ColonneTypeContraintesDescription
iduuidPK, NOT NULLIdentifiant unique
address_idintFK, NOT NULLRéférence à address
has_contractbooleanNOT NULLPossède un contrat
detailstextNULLDétails du problème
availabilitysimple_arrayNULLDisponibilités (tableau)
availability_detailstextNULLDétails sur les disponibilités
statusenumNOT NULLStatut (NEW, IN_PROGRESS, RESOLVED, CANCELLED)
commentstextNULLCommentaires internes
created_atdatetime_immutableNOT NULLDate de création
updated_atdatetime_immutableNOT NULLDate de mise à jour

Relations :

  • OneToOneaddress (Address)
  • ManyToManydiagnosis (Troubleshooting\Diagnosis) via table troubleshooting_diagnosis

diagnosis (Troubleshooting\Diagnosis)

Diagnostics possibles.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom du diagnostic

Référentiels

address (Address)

Adresses (contact et facturation).

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
civility_idintFK, NULLRéférence à civility
namevarchar(255)NULLPrénom
lastnamevarchar(255)NULLNom
is_probooleanNOT NULL, DEFAULT falseClient professionnel
company_namevarchar(255)NULLNom de l'entreprise
siretvarchar(255)NULLNuméro SIRET
vat_numbervarchar(255)NULLNuméro de TVA
streetvarchar(255)NOT NULLRue
additional_infovarchar(255)NULLComplément d'adresse
postal_codevarchar(255)NOT NULLCode postal
cityvarchar(255)NOT NULLVille
emailvarchar(255)NULLEmail
phonevarchar(255)NULLTéléphone

Relations :

  • ManyToOnecivility (Civility)

civility (Civility)

Civilités.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom de la civilité
sav_labelvarchar(255)NULLLabel pour le SAV

postal_code (PostalCode)

Codes postaux avec tarification.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
codevarchar(5)NOT NULL, UNIQUECode postal
labelvarchar(255)NOT NULLLibellé (ville)
pricefloatNULLPrix de base
diagnosis_pricefloatNULLPrix du diagnostic

parameter (Parameter)

Paramètres système.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
codevarchar(255)NOT NULL, UNIQUECode du paramètre
labelvarchar(255)NOT NULLLibellé
valuevarchar(255)NOT NULLValeur

refusal_reason (RefusalReason)

Raisons de refus.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
namevarchar(255)NOT NULLNom de la raison

coefficient_rate (CoefficientRate)

Coefficients de tarification par catégorie.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
category_idintFK, NOT NULLRéférence à equipment_category
min_equipmentintNOT NULLNombre minimum d'équipements
coefficientfloatNOT NULLCoefficient appliqué

Relations :

  • ManyToOnecategory (Equipment\Category)

Authentification

admin (Admin)

Comptes administrateurs.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
emailvarchar(180)NOT NULL, UNIQUEEmail de l'admin
rolesjsonNOT NULLRôles (tableau JSON)
passwordvarchar(255)NOT NULLMot de passe hashé

Relations :

  • OneToManyresetPasswordRequests (ResetPasswordRequest)
  • OneToManyrefreshTokens (RefreshToken)

reset_password_request (ResetPasswordRequest)

Demandes de réinitialisation de mot de passe.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
user_idintFK, NOT NULLRéférence à admin
selectorvarchar(20)NOT NULLSélecteur unique
hashed_tokenvarchar(100)NOT NULLToken hashé
requested_atdatetime_immutableNOT NULLDate de demande
expires_atdatetime_immutableNOT NULLDate d'expiration

Relations :

  • ManyToOneuser (Admin)

refresh_tokens (RefreshToken)

Tokens de rafraîchissement JWT.

ColonneTypeContraintesDescription
idintPK, AUTO_INCREMENTIdentifiant unique
refresh_tokenvarchar(128)NOT NULL, UNIQUEToken de rafraîchissement
usernamevarchar(255)NOT NULLNom d'utilisateur
validdatetime_immutableNOT NULLDate de validité

Tables de jointure

maintenance_offer_feature

Table de jointure entre maintenance_offer et feature.

ColonneTypeContraintes
maintenance_offer_idintFK, PK
feature_idintFK, PK

troubleshooting_diagnosis

Table de jointure entre troubleshooting et diagnosis.

ColonneTypeContraintes
troubleshooting_iduuidFK, PK
diagnosis_idintFK, PK

request_refusal_reason

Table de jointure entre request_refusal et refusal_reason.

ColonneTypeContraintes
request_refusal_iduuidFK, PK
refusal_reason_idintFK, PK

Index recommandés

-- Index sur les clés étrangères
CREATE INDEX idx_request_offer_request ON request_offer(request_id);
CREATE INDEX idx_request_equipment_request ON request_equipment(request_id);
CREATE INDEX idx_request_contract_request ON request_contract(request_id);
CREATE INDEX idx_price_offer ON price(offer_id);

-- Index sur les recherches fréquentes
CREATE INDEX idx_request_status ON request(status);
CREATE INDEX idx_request_deleted_at ON request(deleted_at);
CREATE INDEX idx_request_archived_at ON request(archived_at);
CREATE INDEX idx_discount_code ON discount(code);
CREATE INDEX idx_postal_code_code ON postal_code(code);
CREATE INDEX idx_parameter_code ON parameter(code);

-- Index sur les dates
CREATE INDEX idx_request_created_at ON request(created_at);
CREATE INDEX idx_contract_expired_at ON request_contract(expired_at);
CREATE INDEX idx_troubleshooting_status ON troubleshooting(status);

Contraintes et règles métier

Soft Delete

La table request utilise le soft delete via le champ deleted_at. Les enregistrements supprimés ne sont pas physiquement supprimés mais marqués comme supprimés.

Expiration automatique

  • Les request expirent 4 mois après leur création (expired_at)
  • Les contract expirent 90 jours après leur création (expired_at)

Statuts

RequestStatusEnum :

  • PENDING : En attente
  • WAITING_SIGNATURE : En attente de signature
  • WAITING_PAYMENT : En attente de paiement
  • COMPLETE : Complétée
  • REJECTED : Rejetée

ContractStatusEnum :

  • PENDING : En attente
  • SIGNED : Signé
  • EXPIRED : Expiré
  • CANCELLED : Annulé

TroubleshootingStatusEnum :

  • NEW : Nouveau
  • IN_PROGRESS : En cours
  • RESOLVED : Résolu
  • CANCELLED : Annulé

Migrations

Les modifications de structure sont gérées via Doctrine Migrations :

# Créer une migration après modification des entités
php bin/console make:migration

# Exécuter les migrations
php bin/console doctrine:migrations:migrate

# Voir le statut des migrations
php bin/console doctrine:migrations:status

Fixtures

Les données de référence sont chargées via des fixtures :

# Charger les fixtures
php bin/console doctrine:fixtures:load

# Charger uniquement certaines fixtures
php bin/console doctrine:fixtures:load --group=equipment

Bonnes pratiques

  1. UUID vs Integer
    • Utiliser UUID pour les entités exposées via API (Request, Offer, Contract, Troubleshooting)
    • Utiliser Integer pour les entités référentielles (Category, Type, Brand, etc.)
  2. Soft Delete
    • Toujours vérifier deleted_at IS NULL dans les requêtes
    • Utiliser les filtres Doctrine pour exclure automatiquement les enregistrements supprimés
  3. Timestamps
    • Utiliser datetime_immutable pour éviter les modifications accidentelles
    • Les timestamps sont automatiquement gérés via les lifecycle callbacks
  4. Relations
    • Utiliser cascade: ['persist', 'remove'] avec précaution
    • Toujours définir orphanRemoval: true pour les relations OneToOne/OneToMany
  5. Performance
    • Éviter le N+1 avec fetch: 'EAGER' ou des jointures explicites
    • Utiliser la pagination pour les listes
    • Indexer les colonnes fréquemment interrogées

Liens utiles

Résumé

  • Base de données PostgreSQL avec Doctrine ORM
  • Structure organisée en domaines métier (Maintenance, Troubleshooting, Equipment)
  • Utilisation d'UUID pour les entités principales exposées via API
  • Soft delete pour les demandes
  • Système de statuts et d'expiration automatique
  • Relations complexes avec tables de jointure
  • Index optimisés pour les recherches fréquentes
  • Gestion des migrations et fixtures pour le développement