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.
274 lines
8.8 KiB
Java
274 lines
8.8 KiB
Java
package dev.lions.btpxpress.view;
|
|
|
|
import dev.lions.btpxpress.service.ChantierService;
|
|
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.LocalDate;
|
|
import java.time.LocalDateTime;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.function.Predicate;
|
|
|
|
@Named("chantiersView")
|
|
@ViewScoped
|
|
@Getter
|
|
@Setter
|
|
public class ChantiersView extends BaseListView<ChantiersView.Chantier, Long> implements Serializable {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(ChantiersView.class);
|
|
|
|
@Inject
|
|
ChantierService chantierService;
|
|
|
|
private String filtreNom;
|
|
private String filtreClient;
|
|
private String filtreStatut;
|
|
private Long chantierId;
|
|
|
|
@PostConstruct
|
|
public void init() {
|
|
if (filtreStatut == null) {
|
|
filtreStatut = "TOUS";
|
|
}
|
|
loadItems();
|
|
}
|
|
|
|
/**
|
|
* Définit le filtre de statut (utilisé depuis les pages filtrées).
|
|
*/
|
|
public void setFiltreStatut(String statut) {
|
|
this.filtreStatut = statut;
|
|
}
|
|
|
|
@Override
|
|
public void loadItems() {
|
|
loading = true;
|
|
try {
|
|
items = new ArrayList<>();
|
|
|
|
// Récupération depuis l'API backend
|
|
List<Map<String, Object>> chantiersData = chantierService.getAllChantiers();
|
|
|
|
for (Map<String, Object> data : chantiersData) {
|
|
Chantier c = new Chantier();
|
|
|
|
// Mapping des données de l'API vers l'objet Chantier
|
|
c.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
|
|
c.setNom((String) data.get("nom"));
|
|
|
|
// Le client peut être un objet ou une chaîne
|
|
Object clientObj = data.get("client");
|
|
if (clientObj instanceof Map) {
|
|
Map<String, Object> clientData = (Map<String, Object>) clientObj;
|
|
c.setClient((String) clientData.get("raisonSociale"));
|
|
} else if (clientObj instanceof String) {
|
|
c.setClient((String) clientObj);
|
|
} else {
|
|
c.setClient("N/A");
|
|
}
|
|
|
|
c.setAdresse((String) data.get("adresse"));
|
|
|
|
// Conversion des dates
|
|
if (data.get("dateDebut") != null) {
|
|
c.setDateDebut(LocalDate.parse(data.get("dateDebut").toString()));
|
|
}
|
|
if (data.get("dateFinPrevue") != null) {
|
|
c.setDateFinPrevue(LocalDate.parse(data.get("dateFinPrevue").toString()));
|
|
}
|
|
|
|
c.setStatut((String) data.get("statut"));
|
|
|
|
// Avancement en pourcentage
|
|
Object avancementObj = data.get("avancement");
|
|
if (avancementObj != null) {
|
|
c.setAvancement(avancementObj instanceof Integer ?
|
|
(Integer) avancementObj :
|
|
Integer.parseInt(avancementObj.toString()));
|
|
} else {
|
|
c.setAvancement(0);
|
|
}
|
|
|
|
// Budget et coût réel
|
|
Object montantObj = data.get("montant");
|
|
if (montantObj != null) {
|
|
c.setBudget(montantObj instanceof Number ?
|
|
((Number) montantObj).doubleValue() :
|
|
Double.parseDouble(montantObj.toString()));
|
|
} else {
|
|
c.setBudget(0.0);
|
|
}
|
|
|
|
Object coutReelObj = data.get("coutReel");
|
|
if (coutReelObj != null) {
|
|
c.setCoutReel(coutReelObj instanceof Number ?
|
|
((Number) coutReelObj).doubleValue() :
|
|
Double.parseDouble(coutReelObj.toString()));
|
|
} else {
|
|
c.setCoutReel(0.0);
|
|
}
|
|
|
|
items.add(c);
|
|
}
|
|
|
|
LOG.info("Chantiers chargés depuis l'API : {} élément(s)", items.size());
|
|
applyFilters(items, buildFilters());
|
|
} catch (Exception e) {
|
|
LOG.error("Erreur chargement chantiers depuis l'API", e);
|
|
// En cas d'erreur, on garde une liste vide
|
|
items = new ArrayList<>();
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
}
|
|
|
|
private List<Predicate<Chantier>> buildFilters() {
|
|
List<Predicate<Chantier>> filters = new ArrayList<>();
|
|
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
|
|
filters.add(c -> c.getNom().toLowerCase().contains(filtreNom.toLowerCase()));
|
|
}
|
|
if (filtreClient != null && !filtreClient.trim().isEmpty()) {
|
|
filters.add(c -> c.getClient().toLowerCase().contains(filtreClient.toLowerCase()));
|
|
}
|
|
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
|
|
filters.add(c -> c.getStatut().equals(filtreStatut));
|
|
}
|
|
return filters;
|
|
}
|
|
|
|
@Override
|
|
protected void resetFilterFields() {
|
|
filtreNom = null;
|
|
filtreClient = null;
|
|
filtreStatut = "TOUS";
|
|
}
|
|
|
|
@Override
|
|
protected String getDetailsPath() {
|
|
return "/chantiers/";
|
|
}
|
|
|
|
@Override
|
|
protected String getCreatePath() {
|
|
return "/chantiers/nouveau";
|
|
}
|
|
|
|
@Override
|
|
protected void performDelete() {
|
|
LOG.info("Suppression chantier : {}", selectedItem.getId());
|
|
// TODO: Appeler chantierService.delete(selectedItem.getId())
|
|
}
|
|
|
|
@Override
|
|
protected Chantier createNewEntity() {
|
|
Chantier c = new Chantier();
|
|
c.setStatut("PLANIFIE");
|
|
c.setAvancement(0);
|
|
c.setDateDebut(LocalDate.now());
|
|
c.setDateCreation(LocalDateTime.now());
|
|
return c;
|
|
}
|
|
|
|
@Override
|
|
protected void performCreate() {
|
|
entity.setId(System.currentTimeMillis()); // Simulation ID
|
|
entity.setDateCreation(LocalDateTime.now());
|
|
entity.setDateModification(LocalDateTime.now());
|
|
items.add(entity);
|
|
LOG.info("Nouveau chantier créé : {}", entity.getNom());
|
|
// TODO: Appeler chantierService.create(entity)
|
|
}
|
|
|
|
@Override
|
|
protected void performUpdate() {
|
|
entity.setDateModification(LocalDateTime.now());
|
|
LOG.info("Chantier modifié : {}", entity.getNom());
|
|
// TODO: Appeler chantierService.update(entity)
|
|
}
|
|
|
|
@Override
|
|
protected Long getEntityId(Chantier chantier) {
|
|
return chantier.getId();
|
|
}
|
|
|
|
/**
|
|
* Initialise un nouveau chantier pour la création.
|
|
*/
|
|
@Override
|
|
public String createNew() {
|
|
prepareNew();
|
|
return getCreatePath() + "?faces-redirect=true";
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde un nouveau chantier.
|
|
*/
|
|
public String saveNew() {
|
|
if (selectedItem == null) {
|
|
selectedItem = new Chantier();
|
|
}
|
|
selectedItem.setId(System.currentTimeMillis()); // Simulation ID
|
|
selectedItem.setDateCreation(LocalDateTime.now());
|
|
selectedItem.setDateModification(LocalDateTime.now());
|
|
items.add(selectedItem);
|
|
LOG.info("Nouveau chantier créé : {}", selectedItem.getNom());
|
|
return "/chantiers?faces-redirect=true";
|
|
}
|
|
|
|
/**
|
|
* Charge un chantier par son ID depuis les paramètres de la requête.
|
|
*/
|
|
public void loadChantierById() {
|
|
if (chantierId != null) {
|
|
loadItems(); // S'assurer que les items sont chargés
|
|
selectedItem = items.stream()
|
|
.filter(c -> c.getId().equals(chantierId))
|
|
.findFirst()
|
|
.orElse(null);
|
|
if (selectedItem == null) {
|
|
LOG.warn("Chantier avec ID {} non trouvé", chantierId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche les détails d'un chantier.
|
|
*/
|
|
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 "/chantiers?faces-redirect=true";
|
|
}
|
|
|
|
@lombok.Getter
|
|
@lombok.Setter
|
|
public static class Chantier {
|
|
private Long id;
|
|
private String nom;
|
|
private String client;
|
|
private String adresse;
|
|
private LocalDate dateDebut;
|
|
private LocalDate dateFinPrevue;
|
|
private String statut;
|
|
private int avancement;
|
|
private double budget;
|
|
private double coutReel;
|
|
private LocalDateTime dateCreation;
|
|
private LocalDateTime dateModification;
|
|
}
|
|
}
|