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; /** * Devise du chiffre d'affaires. */ @Column(name = "revenue_currency", length = 3) @Size(min = 3, max = 3, message = "Le code devise doit faire 3 caractères") private String revenueCurrency = "USD"; /** * Locale préférée. */ @Column(name = "preferred_locale", length = 20) @Size(max = 20, message = "La locale ne peut pas dépasser 20 caractères") private String preferredLocale = "fr-FR"; /** * 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; /** * Date de début de service. */ @Column(name = "service_start_date") private LocalDate serviceStartDate; /** * Date de fin de service. */ @Column(name = "service_end_date") private LocalDate serviceEndDate; /** * 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 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 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 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 getRevenueCurrency() { return revenueCurrency; } public void setRevenueCurrency(String revenueCurrency) { this.revenueCurrency = revenueCurrency; } public String getPreferredLocale() { return preferredLocale; } public void setPreferredLocale(String preferredLocale) { this.preferredLocale = preferredLocale; } 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; } public LocalDate getServiceStartDate() { return serviceStartDate; } public void setServiceStartDate(LocalDate serviceStartDate) { this.serviceStartDate = serviceStartDate; } public LocalDate getServiceEndDate() { return serviceEndDate; } public void setServiceEndDate(LocalDate serviceEndDate) { this.serviceEndDate = serviceEndDate; } @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, /** * Client suspendu. */ SUSPENDED, /** * Ancien client. */ FORMER } }