Files
btpxpress-frontend/src/main/java/dev/lions/btpxpress/view/ClientsView.java
dahoud ec38f6a23a feat: Module Devis professionnel avec écrans complets
Création de 2 écrans professionnels pour le module Devis:

1. devis/nouveau.xhtml:
   - 4 sections: Informations générales, Détail du devis, Montants, Conditions
   - Numéro auto-généré avec icône
   - Statut avec 5 valeurs (BROUILLON, ATTENTE, ACCEPTE, REFUSE, EXPIRE)
   - Dates d'émission et validité avec calendriers
   - Client et objet du devis requis
   - Placeholder pour lignes de devis (future développement)
   - Calcul automatique TVA 18% et TTC
   - Récapitulatif visuel HT/TVA/TTC avec composant monétaire
   - Conditions de paiement et remarques (section collapsible)
   - 3 boutons: Annuler, Brouillon, Envoyer

2. devis/details.xhtml:
   - En-tête: numéro, statut, client, objet, dates
   - Actions: Retour, Convertir en chantier, PDF, Modifier
   - 4 KPI cards: Montant HT, TVA, TTC, Statut
   - 6 onglets professionnels:
     * Vue d'ensemble: infos + récap financier + actions rapides
     * Détail des lignes: table lignes (placeholder)
     * Conditions: paiement, délais, garanties
     * Documents: GED associée (placeholder)
     * Suivi: timeline actions
     * Historique: modifications (placeholder)

Corrections:
- Fix navigation /factures/nouvelle -> /factures/nouveau (factures.xhtml)
- Fix menu /factures/nouvelle -> /factures/nouveau (menu.xhtml)

Tous les composants réutilisables utilisés (status-badge, monetary-display).
Validation complète côté client et serveur.
UI/UX professionnel adapté au métier BTP.
2025-11-08 10:49:19 +00:00

251 lines
8.1 KiB
Java

package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.ClientService;
import jakarta.annotation.PostConstruct;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
@Named("clientsView")
@ViewScoped
@Getter
@Setter
public class ClientsView extends BaseListView<ClientsView.Client, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(ClientsView.class);
@Inject
ClientService clientService;
private String filtreNom;
private String filtreEmail;
private String filtreVille;
private Long clientId;
@PostConstruct
public void init() {
loadItems();
}
@Override
public void loadItems() {
loading = true;
try {
items = new ArrayList<>();
// Récupération depuis l'API backend
List<Map<String, Object>> clientsData = clientService.getAllClients();
for (Map<String, Object> data : clientsData) {
Client c = new Client();
// Mapping des données de l'API vers l'objet Client
c.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
// Raison sociale : entreprise ou "Particulier" si vide
String entreprise = (String) data.get("entreprise");
c.setRaisonSociale(entreprise != null && !entreprise.trim().isEmpty() ?
entreprise : "Particulier");
// Nom complet du contact : prénom + nom
String prenom = (String) data.get("prenom");
String nom = (String) data.get("nom");
c.setNomContact((prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
c.setEmail((String) data.get("email"));
c.setTelephone((String) data.get("telephone"));
c.setAdresse((String) data.get("adresse"));
c.setVille((String) data.get("ville"));
c.setCodePostal((String) data.get("codePostal"));
// Nombre de chantiers (relation)
Object chantiersObj = data.get("chantiers");
if (chantiersObj instanceof List) {
c.setNombreChantiers(((List<?>) chantiersObj).size());
} else {
c.setNombreChantiers(0);
}
// Chiffre d'affaires total (à calculer ou récupérer)
// Pour l'instant, on met 0 car cette donnée n'est pas dans l'API
c.setChiffreAffairesTotal(0.0);
// Date de création
if (data.get("dateCreation") != null) {
c.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
// Date de modification
if (data.get("dateModification") != null) {
c.setDateModification(LocalDateTime.parse(data.get("dateModification").toString()));
}
items.add(c);
}
LOG.info("Clients chargés depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement clients depuis l'API", e);
// En cas d'erreur, on garde une liste vide
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Client>> buildFilters() {
List<Predicate<Client>> filters = new ArrayList<>();
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
filters.add(c -> c.getRaisonSociale().toLowerCase().contains(filtreNom.toLowerCase()) ||
c.getNomContact().toLowerCase().contains(filtreNom.toLowerCase()));
}
if (filtreEmail != null && !filtreEmail.trim().isEmpty()) {
filters.add(c -> c.getEmail().toLowerCase().contains(filtreEmail.toLowerCase()));
}
if (filtreVille != null && !filtreVille.trim().isEmpty()) {
filters.add(c -> c.getVille().toLowerCase().contains(filtreVille.toLowerCase()));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreNom = null;
filtreEmail = null;
filtreVille = null;
}
@Override
protected String getDetailsPath() {
return "/clients/";
}
@Override
protected String getCreatePath() {
return "/clients/nouveau";
}
@Override
protected void performDelete() {
LOG.info("Suppression client : {}", selectedItem.getId());
}
@Override
protected Client createNewEntity() {
Client client = new Client();
client.setDateCreation(LocalDateTime.now());
client.setDateModification(LocalDateTime.now());
client.setNombreChantiers(0);
client.setChiffreAffairesTotal(0.0);
return client;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
selectedItem.setDateModification(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
selectedItem.setDateModification(LocalDateTime.now());
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Client entity) {
return entity.getId();
}
/**
* Initialise un nouveau client pour la création.
*/
@Override
public String createNew() {
selectedItem = new Client();
return getCreatePath() + "?faces-redirect=true";
}
/**
* Sauvegarde un nouveau client.
*/
public String saveNew() {
if (selectedItem == null) {
selectedItem = new Client();
}
selectedItem.setId(System.currentTimeMillis());
selectedItem.setDateCreation(LocalDateTime.now());
selectedItem.setDateModification(LocalDateTime.now());
selectedItem.setNombreChantiers(0);
selectedItem.setChiffreAffairesTotal(0.0);
items.add(selectedItem);
LOG.info("Nouveau client créé : {}", selectedItem.getRaisonSociale());
return "/clients?faces-redirect=true";
}
/**
* Affiche les détails d'un client.
*/
public String viewDetails(Long id) {
selectedItem = items.stream()
.filter(c -> c.getId().equals(id))
.findFirst()
.orElse(null);
if (selectedItem != null) {
return getDetailsPath() + "details?id=" + id + "&faces-redirect=true";
}
return "/clients?faces-redirect=true";
}
/**
* Charge un client par son ID depuis les paramètres de la requête.
*/
public void loadClientById() {
if (clientId != null) {
loadItems(); // S'assurer que les items sont chargés
selectedItem = items.stream()
.filter(c -> c.getId().equals(clientId))
.findFirst()
.orElse(null);
if (selectedItem == null) {
LOG.warn("Client avec ID {} non trouvé", clientId);
}
}
}
@lombok.Getter
@lombok.Setter
public static class Client {
private Long id;
private String raisonSociale;
private String nomContact;
private String email;
private String telephone;
private String adresse;
private String ville;
private String codePostal;
private String pays;
private int nombreChantiers;
private double chiffreAffairesTotal;
private LocalDateTime dateCreation;
private LocalDateTime dateModification;
}
}