Task 1.2 - Entités JPA fondamentales

- Création de BaseEntity avec audit trail et soft delete
- Création de l'entité User avec Quarkus Security JPA
- Création de l'entité Client avec informations d'entreprise
- Création de l'entité Coach avec informations professionnelles
- Relations JPA one-to-one entre User-Client et User-Coach
- Migrations Flyway V1, V2, V3 pour les tables
- Données de test dans import.sql
- Compilation réussie du module d'implémentation
This commit is contained in:
dahoud
2025-10-06 20:11:18 +00:00
parent e4d125e14c
commit 9d8ce834e8
10 changed files with 2127 additions and 429 deletions

View File

@@ -0,0 +1,531 @@
package com.gbcm.server.impl.entity;
import com.gbcm.server.api.enums.ServiceType;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* Entité représentant un client de la plateforme GBCM.
* Un client est associé à un utilisateur et peut avoir plusieurs services.
*
* @author GBCM Development Team
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "clients", indexes = {
@Index(name = "idx_clients_user_id", columnList = "user_id", unique = true),
@Index(name = "idx_clients_company", columnList = "company_name"),
@Index(name = "idx_clients_status", columnList = "status"),
@Index(name = "idx_clients_deleted", columnList = "deleted")
})
@NamedQueries({
@NamedQuery(name = "Client.findByUserId",
query = "SELECT c FROM Client c WHERE c.user.id = :userId AND c.deleted = false"),
@NamedQuery(name = "Client.findByStatus",
query = "SELECT c FROM Client c WHERE c.status = :status AND c.deleted = false"),
@NamedQuery(name = "Client.findByCompanyName",
query = "SELECT c FROM Client c WHERE LOWER(c.companyName) LIKE LOWER(:companyName) AND c.deleted = false"),
@NamedQuery(name = "Client.findActiveClients",
query = "SELECT c FROM Client c WHERE c.status = 'ACTIVE' AND c.deleted = false")
})
public class Client extends BaseEntity {
/**
* Identifiant unique du client.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Utilisateur associé à ce client.
* Relation one-to-one obligatoire.
*/
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false, unique = true,
foreignKey = @ForeignKey(name = "fk_clients_user_id"))
@NotNull(message = "L'utilisateur associé est obligatoire")
private User user;
/**
* Nom de l'entreprise du client.
*/
@Column(name = "company_name", nullable = false, length = 200)
@NotBlank(message = "Le nom de l'entreprise est obligatoire")
@Size(max = 200, message = "Le nom de l'entreprise ne peut pas dépasser 200 caractères")
private String companyName;
/**
* Secteur d'activité de l'entreprise.
*/
@Column(name = "industry", length = 100)
@Size(max = 100, message = "Le secteur d'activité ne peut pas dépasser 100 caractères")
private String industry;
/**
* Taille de l'entreprise (nombre d'employés).
*/
@Column(name = "company_size")
private Integer companySize;
/**
* Chiffre d'affaires annuel de l'entreprise.
*/
@Column(name = "annual_revenue", precision = 15, scale = 2)
private BigDecimal annualRevenue;
/**
* Adresse de l'entreprise - ligne 1.
*/
@Column(name = "address_line1", length = 255)
@Size(max = 255, message = "L'adresse ligne 1 ne peut pas dépasser 255 caractères")
private String addressLine1;
/**
* Adresse de l'entreprise - ligne 2.
*/
@Column(name = "address_line2", length = 255)
@Size(max = 255, message = "L'adresse ligne 2 ne peut pas dépasser 255 caractères")
private String addressLine2;
/**
* Ville de l'entreprise.
*/
@Column(name = "city", length = 100)
@Size(max = 100, message = "La ville ne peut pas dépasser 100 caractères")
private String city;
/**
* État/Province de l'entreprise.
*/
@Column(name = "state", length = 100)
@Size(max = 100, message = "L'état ne peut pas dépasser 100 caractères")
private String state;
/**
* Code postal de l'entreprise.
*/
@Column(name = "postal_code", length = 20)
@Size(max = 20, message = "Le code postal ne peut pas dépasser 20 caractères")
private String postalCode;
/**
* Pays de l'entreprise.
*/
@Column(name = "country", length = 100)
@Size(max = 100, message = "Le pays ne peut pas dépasser 100 caractères")
private String country;
/**
* Site web de l'entreprise.
*/
@Column(name = "website", length = 255)
@Size(max = 255, message = "Le site web ne peut pas dépasser 255 caractères")
private String website;
/**
* Statut du client.
*/
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 20)
@NotNull(message = "Le statut est obligatoire")
private ClientStatus status = ClientStatus.PROSPECT;
/**
* Date de conversion de prospect à client.
*/
@Column(name = "converted_at")
private LocalDateTime convertedAt;
/**
* Type de service principal du client.
*/
@Enumerated(EnumType.STRING)
@Column(name = "primary_service_type", length = 30)
private ServiceType primaryServiceType;
/**
* Valeur totale des contrats du client.
*/
@Column(name = "total_contract_value", precision = 15, scale = 2)
private BigDecimal totalContractValue = BigDecimal.ZERO;
/**
* Date de début de la relation client.
*/
@Column(name = "relationship_start_date")
private LocalDate relationshipStartDate;
/**
* Notes internes sur le client.
*/
@Column(name = "notes", columnDefinition = "TEXT")
private String notes;
/**
* Source d'acquisition du client.
*/
@Column(name = "acquisition_source", length = 100)
@Size(max = 100, message = "La source d'acquisition ne peut pas dépasser 100 caractères")
private String acquisitionSource;
/**
* Constructeur par défaut.
*/
public Client() {
super();
}
/**
* Constructeur avec les champs obligatoires.
*
* @param user l'utilisateur associé
* @param companyName le nom de l'entreprise
*/
public Client(User user, String companyName) {
this();
this.user = user;
this.companyName = companyName;
}
/**
* Convertit un prospect en client.
*/
public void convertToClient() {
if (this.status == ClientStatus.PROSPECT) {
this.status = ClientStatus.ACTIVE;
this.convertedAt = LocalDateTime.now();
this.relationshipStartDate = LocalDate.now();
}
}
/**
* Désactive le client.
*/
public void deactivate() {
this.status = ClientStatus.INACTIVE;
}
/**
* Réactive le client.
*/
public void reactivate() {
this.status = ClientStatus.ACTIVE;
}
/**
* Ajoute une valeur au contrat total.
*
* @param amount le montant à ajouter
*/
public void addContractValue(BigDecimal amount) {
if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
this.totalContractValue = this.totalContractValue.add(amount);
}
}
/**
* Retourne l'adresse complète formatée.
*
* @return l'adresse complète
*/
public String getFullAddress() {
StringBuilder address = new StringBuilder();
if (addressLine1 != null && !addressLine1.trim().isEmpty()) {
address.append(addressLine1);
}
if (addressLine2 != null && !addressLine2.trim().isEmpty()) {
if (address.length() > 0) address.append(", ");
address.append(addressLine2);
}
if (city != null && !city.trim().isEmpty()) {
if (address.length() > 0) address.append(", ");
address.append(city);
}
if (state != null && !state.trim().isEmpty()) {
if (address.length() > 0) address.append(", ");
address.append(state);
}
if (postalCode != null && !postalCode.trim().isEmpty()) {
if (address.length() > 0) address.append(" ");
address.append(postalCode);
}
if (country != null && !country.trim().isEmpty()) {
if (address.length() > 0) address.append(", ");
address.append(country);
}
return address.toString();
}
/**
* Vérifie si le client est actif.
*
* @return true si le client est actif, false sinon
*/
public boolean isActiveClient() {
return status == ClientStatus.ACTIVE;
}
/**
* Vérifie si le client est un prospect.
*
* @return true si c'est un prospect, false sinon
*/
public boolean isProspect() {
return status == ClientStatus.PROSPECT;
}
/**
* Méthode de recherche par ID utilisateur.
*
* @param userId l'ID de l'utilisateur
* @return le client trouvé ou null
*/
public static Client findByUserId(Long userId) {
return find("#Client.findByUserId", userId).firstResult();
}
/**
* Méthode de recherche par statut.
*
* @param status le statut à rechercher
* @return la liste des clients avec ce statut
*/
public static List<Client> findByStatus(ClientStatus status) {
return find("#Client.findByStatus", status).list();
}
/**
* Méthode de recherche par nom d'entreprise.
*
* @param companyName le nom d'entreprise à rechercher
* @return la liste des clients correspondants
*/
public static List<Client> findByCompanyName(String companyName) {
String searchPattern = "%" + companyName + "%";
return find("#Client.findByCompanyName", searchPattern).list();
}
/**
* Méthode de recherche des clients actifs.
*
* @return la liste des clients actifs
*/
public static List<Client> findActiveClients() {
return find("#Client.findActiveClients").list();
}
// Getters et Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public Integer getCompanySize() {
return companySize;
}
public void setCompanySize(Integer companySize) {
this.companySize = companySize;
}
public BigDecimal getAnnualRevenue() {
return annualRevenue;
}
public void setAnnualRevenue(BigDecimal annualRevenue) {
this.annualRevenue = annualRevenue;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public ClientStatus getStatus() {
return status;
}
public void setStatus(ClientStatus status) {
this.status = status;
}
public LocalDateTime getConvertedAt() {
return convertedAt;
}
public void setConvertedAt(LocalDateTime convertedAt) {
this.convertedAt = convertedAt;
}
public ServiceType getPrimaryServiceType() {
return primaryServiceType;
}
public void setPrimaryServiceType(ServiceType primaryServiceType) {
this.primaryServiceType = primaryServiceType;
}
public BigDecimal getTotalContractValue() {
return totalContractValue;
}
public void setTotalContractValue(BigDecimal totalContractValue) {
this.totalContractValue = totalContractValue;
}
public LocalDate getRelationshipStartDate() {
return relationshipStartDate;
}
public void setRelationshipStartDate(LocalDate relationshipStartDate) {
this.relationshipStartDate = relationshipStartDate;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public String getAcquisitionSource() {
return acquisitionSource;
}
public void setAcquisitionSource(String acquisitionSource) {
this.acquisitionSource = acquisitionSource;
}
@Override
public String toString() {
return "Client{" +
"id=" + id +
", companyName='" + companyName + '\'' +
", industry='" + industry + '\'' +
", status=" + status +
", totalContractValue=" + totalContractValue +
", relationshipStartDate=" + relationshipStartDate +
'}';
}
/**
* Énumération des statuts de client.
*/
public enum ClientStatus {
/**
* Prospect - pas encore client.
*/
PROSPECT,
/**
* Client actif.
*/
ACTIVE,
/**
* Client inactif.
*/
INACTIVE,
/**
* Ancien client.
*/
FORMER
}
}