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.
This commit is contained in:
dahoud
2025-11-08 10:49:19 +00:00
parent 0fad42ccaf
commit ec38f6a23a
192 changed files with 12029 additions and 271 deletions

View File

@@ -0,0 +1,116 @@
package dev.lions.btpxpress.filter;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Filtre de sécurité qui ajoute les headers HTTP de sécurité essentiels
* pour protéger l'application contre diverses attaques.
*/
public class SecurityHeadersFilter implements Filter {
private static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security";
private static final String X_FRAME_OPTIONS = "X-Frame-Options";
private static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";
private static final String X_XSS_PROTECTION = "X-XSS-Protection";
private static final String REFERRER_POLICY = "Referrer-Policy";
private static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy";
private static final String PERMISSIONS_POLICY = "Permissions-Policy";
// HSTS - Force HTTPS pendant 1 an, inclut les sous-domaines
private static final String HSTS_VALUE = "max-age=31536000; includeSubDomains; preload";
// X-Frame-Options - Empêche le clickjacking
private static final String X_FRAME_OPTIONS_VALUE = "DENY";
// X-Content-Type-Options - Empêche le MIME sniffing
private static final String X_CONTENT_TYPE_OPTIONS_VALUE = "nosniff";
// X-XSS-Protection - Active la protection XSS du navigateur (legacy mais utile)
private static final String X_XSS_PROTECTION_VALUE = "1; mode=block";
// Referrer-Policy - Contrôle les informations de referrer envoyées
private static final String REFERRER_POLICY_VALUE = "strict-origin-when-cross-origin";
// Content Security Policy - Politique de sécurité stricte
// Autorise uniquement les ressources depuis le même domaine et security.lions.dev
private static final String CSP_VALUE =
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://security.lions.dev; " +
"style-src 'self' 'unsafe-inline' https://security.lions.dev; " +
"img-src 'self' data: https: blob:; " +
"font-src 'self' data: https://security.lions.dev; " +
"connect-src 'self' https://security.lions.dev https://api.btpxpress.lions.dev https://api.lions.dev; " +
"frame-src 'self' https://security.lions.dev; " +
"object-src 'none'; " +
"base-uri 'self'; " +
"form-action 'self' https://security.lions.dev; " +
"frame-ancestors 'none'; " +
"upgrade-insecure-requests;";
// Permissions Policy - Désactive les fonctionnalités non nécessaires
private static final String PERMISSIONS_POLICY_VALUE =
"geolocation=(), " +
"microphone=(), " +
"camera=(), " +
"payment=(), " +
"usb=(), " +
"magnetometer=(), " +
"gyroscope=(), " +
"speaker=()";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialisation non nécessaire
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Ajouter les headers de sécurité uniquement pour les requêtes HTTPS
if (httpRequest.isSecure() ||
"https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Proto")) ||
"https".equalsIgnoreCase(httpRequest.getHeader("X-Forwarded-Scheme"))) {
// Strict Transport Security (HSTS)
httpResponse.setHeader(STRICT_TRANSPORT_SECURITY, HSTS_VALUE);
// Content Security Policy
httpResponse.setHeader(CONTENT_SECURITY_POLICY, CSP_VALUE);
}
// Headers de sécurité applicables même en HTTP (développement)
// Ces headers seront toujours présents
httpResponse.setHeader(X_FRAME_OPTIONS, X_FRAME_OPTIONS_VALUE);
httpResponse.setHeader(X_CONTENT_TYPE_OPTIONS, X_CONTENT_TYPE_OPTIONS_VALUE);
httpResponse.setHeader(X_XSS_PROTECTION, X_XSS_PROTECTION_VALUE);
httpResponse.setHeader(REFERRER_POLICY, REFERRER_POLICY_VALUE);
httpResponse.setHeader(PERMISSIONS_POLICY, PERMISSIONS_POLICY_VALUE);
// Headers supplémentaires pour renforcer la sécurité
httpResponse.setHeader("X-Permitted-Cross-Domain-Policies", "none");
httpResponse.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
httpResponse.setHeader("Cross-Origin-Opener-Policy", "same-origin");
httpResponse.setHeader("Cross-Origin-Resource-Policy", "same-origin");
chain.doFilter(request, response);
}
@Override
public void destroy() {
// Nettoyage non nécessaire
}
}

View File

@@ -0,0 +1,82 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des clients côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux clients. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class ClientService {
private static final Logger LOG = LoggerFactory.getLogger(ClientService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère tous les clients depuis l'API backend.
*
* @return Liste des clients, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllClients() {
try {
LOG.debug("Récupération de la liste des clients depuis l'API backend.");
Response response = apiClient.getClients();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> clients = response.readEntity(List.class);
LOG.debug("Clients récupérés avec succès : {} élément(s)", clients != null ? clients.size() : 0);
return clients != null ? clients : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des clients. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les clients : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
/**
* Récupère un client par son identifiant depuis l'API backend.
*
* @param id L'identifiant du client.
* @return Le client sous forme de Map, ou null en cas d'erreur.
*/
public Map<String, Object> getClientById(Long id) {
try {
LOG.debug("Récupération du client avec ID : {}", id);
Response response = apiClient.getClient(id);
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
Map<String, Object> client = response.readEntity(Map.class);
LOG.debug("Client récupéré avec succès.");
return client;
} else {
LOG.warn("Erreur lors de la récupération du client. Code HTTP : {}", response.getStatus());
return null;
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer le client : {}", e.getMessage(), e);
return null;
}
}
}

View File

@@ -0,0 +1,58 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des devis côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux devis. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class DevisService {
private static final Logger LOG = LoggerFactory.getLogger(DevisService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère tous les devis depuis l'API backend.
*
* @return Liste des devis, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllDevis() {
try {
LOG.debug("Récupération de la liste des devis depuis l'API backend.");
Response response = apiClient.getDevis();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> devis = response.readEntity(List.class);
LOG.debug("Devis récupérés avec succès : {} élément(s)", devis != null ? devis.size() : 0);
return devis != null ? devis : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des devis. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les devis : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,58 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des employés côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux employés. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class EmployeService {
private static final Logger LOG = LoggerFactory.getLogger(EmployeService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère tous les employés depuis l'API backend.
*
* @return Liste des employés, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllEmployes() {
try {
LOG.debug("Récupération de la liste des employés depuis l'API backend.");
Response response = apiClient.getEmployes();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> employes = response.readEntity(List.class);
LOG.debug("Employés récupérés avec succès : {} élément(s)", employes != null ? employes.size() : 0);
return employes != null ? employes : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des employés. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les employés : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,58 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des équipes côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux équipes. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class EquipeService {
private static final Logger LOG = LoggerFactory.getLogger(EquipeService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère toutes les équipes depuis l'API backend.
*
* @return Liste des équipes, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllEquipes() {
try {
LOG.debug("Récupération de la liste des équipes depuis l'API backend.");
Response response = apiClient.getEquipes();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> equipes = response.readEntity(List.class);
LOG.debug("Équipes récupérées avec succès : {} élément(s)", equipes != null ? equipes.size() : 0);
return equipes != null ? equipes : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des équipes. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les équipes : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,58 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des factures côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux factures. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class FactureService {
private static final Logger LOG = LoggerFactory.getLogger(FactureService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère toutes les factures depuis l'API backend.
*
* @return Liste des factures, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllFactures() {
try {
LOG.debug("Récupération de la liste des factures depuis l'API backend.");
Response response = apiClient.getFactures();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> factures = response.readEntity(List.class);
LOG.debug("Factures récupérées avec succès : {} élément(s)", factures != null ? factures.size() : 0);
return factures != null ? factures : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des factures. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les factures : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,58 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des matériels côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux matériels. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class MaterielService {
private static final Logger LOG = LoggerFactory.getLogger(MaterielService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère tous les matériels depuis l'API backend.
*
* @return Liste des matériels, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllMateriels() {
try {
LOG.debug("Récupération de la liste des matériels depuis l'API backend.");
Response response = apiClient.getMateriels();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> materiels = response.readEntity(List.class);
LOG.debug("Matériels récupérés avec succès : {} élément(s)", materiels != null ? materiels.size() : 0);
return materiels != null ? materiels : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des matériels. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les matériels : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,61 @@
package dev.lions.btpxpress.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Service de gestion des stocks côté client.
* <p>
* Ce service encapsule la communication avec l'API backend pour les opérations
* liées aux stocks. Il utilise le REST Client pour effectuer les appels HTTP
* vers le backend.
* </p>
*
* @author BTP Xpress Development Team
* @version 1.0.0
* @since 1.0.0
*/
@ApplicationScoped
public class StockService {
private static final Logger LOG = LoggerFactory.getLogger(StockService.class);
@Inject
@RestClient
BtpXpressApiClient apiClient;
/**
* Récupère tous les stocks depuis l'API backend.
*
* @return Liste des stocks, ou liste vide en cas d'erreur.
*/
public List<Map<String, Object>> getAllStocks() {
try {
LOG.debug("Récupération de la liste des stocks depuis l'API backend.");
Response response = apiClient.getStocks();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
// Le backend retourne un objet avec une propriété "stocks"
@SuppressWarnings("unchecked")
Map<String, Object> data = response.readEntity(Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> stocks = (List<Map<String, Object>>) data.get("stocks");
LOG.debug("Stocks récupérés avec succès : {} élément(s)", stocks != null ? stocks.size() : 0);
return stocks != null ? stocks : new ArrayList<>();
} else {
LOG.warn("Erreur lors de la récupération des stocks. Code HTTP : {}", response.getStatus());
return new ArrayList<>();
}
} catch (Exception e) {
LOG.error("Erreur lors de la communication avec l'API backend pour récupérer les stocks : {}", e.getMessage(), e);
return new ArrayList<>();
}
}
}

View File

@@ -1,72 +1,383 @@
package dev.lions.btpxpress.view;
import jakarta.faces.view.ViewScoped;
import jakarta.annotation.PostConstruct;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.List;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Classe de base pour les vues de type liste/CRUD.
*
* Fonctionnalités:
* - Chargement et affichage de listes
* - Filtrage multi-critères
* - Tri (ascendant/descendant)
* - Pagination
* - CRUD complet (Create, Read, Update, Delete)
* - Sélection simple/multiple
* - Messages utilisateur (succès, erreur, warning)
* - Lazy loading pour grandes listes
*
* Principe DRY: Toute la logique commune des écrans de liste est centralisée ici.
*
* @param <T> Type d'entité
* @param <ID> Type de l'identifiant
*/
@Getter
@Setter
public abstract class BaseListView<T, ID> implements Serializable {
protected static final Logger LOG = LoggerFactory.getLogger(BaseListView.class);
private static final long serialVersionUID = 1L;
protected List<T> items = new java.util.ArrayList<>();
// ========== Données ==========
protected List<T> items = new ArrayList<>();
protected List<T> filteredItems = new ArrayList<>();
protected T selectedItem;
protected boolean loading = false;
protected List<T> selectedItems = new ArrayList<>();
protected T entity; // Pour les formulaires create/edit
public abstract void loadItems();
protected void applyFilters(List<T> items, List<Predicate<T>> filters) {
if (filters != null && !filters.isEmpty()) {
filters.stream()
.filter(p -> p != null)
.forEach(filter -> items.removeIf(filter.negate()));
// ========== États ==========
protected boolean loading = false;
protected boolean editing = false; // Mode édition vs création
protected String globalFilter; // Recherche globale
// ========== Pagination ==========
protected int first = 0; // Index de départ
protected int pageSize = 10; // Taille de page
protected int totalRecords = 0; // Nombre total d'enregistrements
// ========== Tri ==========
protected String sortField; // Champ de tri
protected boolean sortAscending = true; // Ordre de tri
// ========== Sélection ==========
protected String selectionMode = "single"; // single, multiple, checkbox
/**
* Initialisation du bean au chargement de la page.
*/
@PostConstruct
public void init() {
LOG.debug("Initialisation de {}", getClass().getSimpleName());
try {
initializeFields();
loadItems();
} catch (Exception e) {
LOG.error("Erreur lors de l'initialisation", e);
addErrorMessage("Erreur lors du chargement des données");
}
}
public void search() {
LOG.debug("Recherche lancée pour {}", getClass().getSimpleName());
/**
* Initialiser les champs spécifiques de la vue.
* Override si nécessaire.
*/
protected void initializeFields() {
// À surcharger dans les classes filles si besoin
}
/**
* Charger les items depuis la source de données.
* DOIT être implémenté par les classes filles.
*/
public abstract void loadItems();
/**
* Recharger les données (alias pour loadItems).
*/
public void refresh() {
LOG.debug("Rafraîchissement des données");
loadItems();
}
// ========== Filtrage ==========
/**
* Appliquer les filtres à la liste d'items.
*/
protected void applyFilters(List<T> sourceItems, List<Predicate<T>> filters) {
if (filters == null || filters.isEmpty()) {
filteredItems = new ArrayList<>(sourceItems);
return;
}
filteredItems = sourceItems.stream()
.filter(filters.stream().reduce(Predicate::and).orElse(x -> true))
.collect(Collectors.toList());
}
/**
* Recherche avec les critères de filtrage actuels.
*/
public void search() {
LOG.debug("Recherche lancée pour {}", getClass().getSimpleName());
first = 0; // Retour à la première page
loadItems();
}
/**
* Réinitialiser tous les filtres.
*/
public void resetFilters() {
LOG.debug("Réinitialisation des filtres pour {}", getClass().getSimpleName());
globalFilter = null;
sortField = null;
sortAscending = true;
first = 0;
resetFilterFields();
loadItems();
}
/**
* Réinitialiser les champs de filtre spécifiques.
* DOIT être implémenté par les classes filles.
*/
protected abstract void resetFilterFields();
public String viewDetails(ID id) {
LOG.debug("Redirection vers détails : {}", id);
return getDetailsPath() + id + "?faces-redirect=true";
// ========== Tri ==========
/**
* Trier la liste par un champ donné.
*/
public void sort(String field) {
if (field.equals(sortField)) {
sortAscending = !sortAscending;
} else {
sortField = field;
sortAscending = true;
}
LOG.debug("Tri par {} ({})", field, sortAscending ? "ASC" : "DESC");
loadItems();
}
// ========== Navigation ==========
/**
* Naviguer vers la page de détails d'un item.
*/
public String viewDetails(ID id) {
LOG.debug("Redirection vers détails : {}", id);
return getDetailsPath() + "?id=" + id + "&faces-redirect=true";
}
/**
* Naviguer vers la page de détails de l'item sélectionné.
*/
public String viewSelectedDetails() {
if (selectedItem == null) {
addWarningMessage("Aucun élément sélectionné");
return null;
}
return viewDetails(getEntityId(selectedItem));
}
/**
* Obtenir le chemin de la page de détails.
*/
protected abstract String getDetailsPath();
/**
* Naviguer vers la page de création.
*/
public String createNew() {
LOG.debug("Redirection vers création");
return getCreatePath() + "?faces-redirect=true";
}
/**
* Obtenir le chemin de la page de création.
*/
protected abstract String getCreatePath();
// ========== CRUD ==========
/**
* Préparer un nouvel item pour création.
*/
public void prepareNew() {
LOG.debug("Préparation nouvelle entité");
entity = createNewEntity();
editing = false;
}
/**
* Créer une nouvelle instance de l'entité.
* DOIT être implémenté par les classes filles.
*/
protected abstract T createNewEntity();
/**
* Préparer un item pour édition.
*/
public void prepareEdit(T item) {
LOG.debug("Préparation édition : {}", item);
entity = item;
editing = true;
}
/**
* Sauvegarder l'entité (création ou modification).
*/
public void save() {
try {
loading = true;
if (editing) {
performUpdate();
addSuccessMessage("Modification réussie");
} else {
performCreate();
addSuccessMessage("Création réussie");
}
loadItems();
entity = null;
editing = false;
} catch (Exception e) {
LOG.error("Erreur lors de la sauvegarde", e);
addErrorMessage("Erreur lors de la sauvegarde : " + e.getMessage());
} finally {
loading = false;
}
}
/**
* Créer une nouvelle entité.
* DOIT être implémenté par les classes filles.
*/
protected abstract void performCreate();
/**
* Mettre à jour une entité existante.
* DOIT être implémenté par les classes filles.
*/
protected abstract void performUpdate();
/**
* Supprimer l'item sélectionné.
*/
public void delete() {
if (selectedItem != null) {
if (selectedItem == null) {
addWarningMessage("Aucun élément sélectionné");
return;
}
try {
loading = true;
LOG.info("Suppression : {}", selectedItem);
performDelete();
items.remove(selectedItem);
selectedItem = null;
addSuccessMessage("Suppression réussie");
loadItems();
} catch (Exception e) {
LOG.error("Erreur lors de la suppression", e);
addErrorMessage("Erreur lors de la suppression : " + e.getMessage());
} finally {
loading = false;
}
}
/**
* Supprimer les items sélectionnés (sélection multiple).
*/
public void deleteSelected() {
if (selectedItems == null || selectedItems.isEmpty()) {
addWarningMessage("Aucun élément sélectionné");
return;
}
try {
loading = true;
int count = selectedItems.size();
for (T item : selectedItems) {
selectedItem = item;
performDelete();
}
loadItems();
selectedItems.clear();
selectedItem = null;
addSuccessMessage(count + " élément(s) supprimé(s)");
} catch (Exception e) {
LOG.error("Erreur lors de la suppression multiple", e);
addErrorMessage("Erreur lors de la suppression");
} finally {
loading = false;
}
}
/**
* Effectuer la suppression réelle.
* DOIT être implémenté par les classes filles.
*/
protected abstract void performDelete();
/**
* Obtenir l'ID d'une entité.
* DOIT être implémenté par les classes filles.
*/
protected abstract ID getEntityId(T entity);
// ========== Messages utilisateur ==========
protected void addSuccessMessage(String message) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
}
protected void addErrorMessage(String message) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
}
protected void addWarningMessage(String message) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", message));
}
protected void addInfoMessage(String message) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
}
// ========== Utilitaires ==========
/**
* Vérifier si la liste est vide.
*/
public boolean isEmpty() {
return items == null || items.isEmpty();
}
/**
* Obtenir le nombre d'items.
*/
public int getItemCount() {
return items == null ? 0 : items.size();
}
/**
* Vérifier si un item est sélectionné.
*/
public boolean hasSelection() {
return selectedItem != null;
}
/**
* Vérifier si plusieurs items sont sélectionnés.
*/
public boolean hasMultipleSelection() {
return selectedItems != null && !selectedItems.isEmpty();
}
}

View File

@@ -165,6 +165,39 @@ public class ChantiersView extends BaseListView<ChantiersView.Chantier, Long> im
@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();
}
/**
@@ -172,10 +205,7 @@ public class ChantiersView extends BaseListView<ChantiersView.Chantier, Long> im
*/
@Override
public String createNew() {
selectedItem = new Chantier();
selectedItem.setStatut("PLANIFIE");
selectedItem.setAvancement(0);
selectedItem.setDateDebut(LocalDate.now());
prepareNew();
return getCreatePath() + "?faces-redirect=true";
}

View File

@@ -142,6 +142,38 @@ public class ClientsView extends BaseListView<ClientsView.Client, Long> implemen
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.
*/

View File

@@ -0,0 +1,270 @@
package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.DevisService;
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.math.BigDecimal;
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("devisView")
@ViewScoped
@Getter
@Setter
public class DevisView extends BaseListView<DevisView.Devis, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(DevisView.class);
@Inject
DevisService devisService;
private String filtreNumero;
private String filtreClient;
private String filtreStatut;
private Long devisId;
@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>> devisData = devisService.getAllDevis();
for (Map<String, Object> data : devisData) {
Devis d = new Devis();
// Mapping des données de l'API vers l'objet Devis
d.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
d.setNumero((String) data.get("numero"));
d.setObjet((String) data.get("objet"));
// 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;
String entreprise = (String) clientData.get("entreprise");
String nom = (String) clientData.get("nom");
String prenom = (String) clientData.get("prenom");
d.setClient(entreprise != null && !entreprise.trim().isEmpty() ?
entreprise : (prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
} else if (clientObj instanceof String) {
d.setClient((String) clientObj);
} else {
d.setClient("N/A");
}
// Conversion des dates
if (data.get("dateEmission") != null) {
d.setDateEmission(LocalDate.parse(data.get("dateEmission").toString()));
}
if (data.get("dateValidite") != null) {
d.setDateValidite(LocalDate.parse(data.get("dateValidite").toString()));
}
d.setStatut((String) data.get("statut"));
// Montants
Object montantHTObj = data.get("montantHT");
if (montantHTObj != null) {
d.setMontantHT(montantHTObj instanceof Number ?
((Number) montantHTObj).doubleValue() :
Double.parseDouble(montantHTObj.toString()));
} else {
d.setMontantHT(0.0);
}
Object montantTTCObj = data.get("montantTTC");
if (montantTTCObj != null) {
d.setMontantTTC(montantTTCObj instanceof Number ?
((Number) montantTTCObj).doubleValue() :
Double.parseDouble(montantTTCObj.toString()));
} else {
d.setMontantTTC(0.0);
}
if (data.get("dateCreation") != null) {
d.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
items.add(d);
}
LOG.info("Devis chargés depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement devis depuis l'API", e);
// En cas d'erreur, on garde une liste vide
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Devis>> buildFilters() {
List<Predicate<Devis>> filters = new ArrayList<>();
if (filtreNumero != null && !filtreNumero.trim().isEmpty()) {
filters.add(d -> d.getNumero().toLowerCase().contains(filtreNumero.toLowerCase()));
}
if (filtreClient != null && !filtreClient.trim().isEmpty()) {
filters.add(d -> d.getClient().toLowerCase().contains(filtreClient.toLowerCase()));
}
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
filters.add(d -> d.getStatut().equals(filtreStatut));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreNumero = null;
filtreClient = null;
filtreStatut = "TOUS";
}
@Override
protected String getDetailsPath() {
return "/devis/";
}
@Override
protected String getCreatePath() {
return "/devis/nouveau";
}
@Override
protected void performDelete() {
LOG.info("Suppression devis : {}", selectedItem.getId());
}
@Override
protected Devis createNewEntity() {
Devis devis = new Devis();
devis.setStatut("BROUILLON");
devis.setDateEmission(LocalDate.now());
devis.setDateValidite(LocalDate.now().plusDays(30));
devis.setMontantHT(0.0);
devis.setMontantTTC(0.0);
devis.setDateCreation(LocalDateTime.now());
return devis;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Devis entity) {
return entity.getId();
}
/**
* Initialise un nouveau devis pour la création.
*/
@Override
public String createNew() {
selectedItem = new Devis();
selectedItem.setStatut("BROUILLON");
selectedItem.setDateEmission(LocalDate.now());
selectedItem.setDateValidite(LocalDate.now().plusDays(30));
return getCreatePath() + "?faces-redirect=true";
}
/**
* Sauvegarde un nouveau devis.
*/
public String saveNew() {
if (selectedItem == null) {
selectedItem = new Devis();
}
selectedItem.setId(System.currentTimeMillis()); // Simulation ID
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Nouveau devis créé : {}", selectedItem.getNumero());
return "/devis?faces-redirect=true";
}
/**
* Charge un devis par son ID depuis les paramètres de la requête.
*/
public void loadDevisById() {
if (devisId != null) {
loadItems(); // S'assurer que les items sont chargés
selectedItem = items.stream()
.filter(d -> d.getId().equals(devisId))
.findFirst()
.orElse(null);
if (selectedItem == null) {
LOG.warn("Devis avec ID {} non trouvé", devisId);
}
}
}
/**
* Affiche les détails d'un devis.
*/
public String viewDetails(Long id) {
selectedItem = items.stream()
.filter(d -> d.getId().equals(id))
.findFirst()
.orElse(null);
if (selectedItem != null) {
return getDetailsPath() + "details?id=" + id + "&faces-redirect=true";
}
return "/devis?faces-redirect=true";
}
@lombok.Getter
@lombok.Setter
public static class Devis {
private Long id;
private String numero;
private String objet;
private String client;
private LocalDate dateEmission;
private LocalDate dateValidite;
private String statut;
private double montantHT;
private double montantTTC;
private LocalDateTime dateCreation;
}
}

View File

@@ -0,0 +1,191 @@
package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.EmployeService;
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("employeView")
@ViewScoped
@Getter
@Setter
public class EmployeView extends BaseListView<EmployeView.Employe, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(EmployeView.class);
@Inject
EmployeService employeService;
private String filtreNom;
private String filtrePoste;
private String filtreStatut;
private Long employeId;
@PostConstruct
public void init() {
if (filtreStatut == null) {
filtreStatut = "TOUS";
}
loadItems();
}
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>> employesData = employeService.getAllEmployes();
for (Map<String, Object> data : employesData) {
Employe e = new Employe();
e.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
String nom = (String) data.get("nom");
String prenom = (String) data.get("prenom");
e.setNomComplet((prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
e.setEmail((String) data.get("email"));
e.setTelephone((String) data.get("telephone"));
e.setPoste((String) data.get("poste"));
e.setStatut((String) data.get("statut"));
// Taux horaire
Object tauxObj = data.get("tauxHoraire");
if (tauxObj != null) {
e.setTauxHoraire(tauxObj instanceof Number ?
((Number) tauxObj).doubleValue() :
Double.parseDouble(tauxObj.toString()));
} else {
e.setTauxHoraire(0.0);
}
// Date d'embauche
if (data.get("dateEmbauche") != null) {
e.setDateEmbauche(LocalDate.parse(data.get("dateEmbauche").toString()));
}
if (data.get("dateCreation") != null) {
e.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
items.add(e);
}
LOG.info("Employés chargés depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement employés depuis l'API", e);
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Employe>> buildFilters() {
List<Predicate<Employe>> filters = new ArrayList<>();
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
filters.add(e -> e.getNomComplet().toLowerCase().contains(filtreNom.toLowerCase()));
}
if (filtrePoste != null && !filtrePoste.trim().isEmpty()) {
filters.add(e -> e.getPoste() != null && e.getPoste().toLowerCase().contains(filtrePoste.toLowerCase()));
}
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
filters.add(e -> e.getStatut().equals(filtreStatut));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreNom = null;
filtrePoste = null;
filtreStatut = "TOUS";
}
@Override
protected String getDetailsPath() {
return "/employes/";
}
@Override
protected String getCreatePath() {
return "/employes/nouveau";
}
@Override
protected void performDelete() {
LOG.info("Suppression employé : {}", selectedItem.getId());
}
@Override
protected Employe createNewEntity() {
Employe employe = new Employe();
employe.setStatut("ACTIF");
employe.setDateEmbauche(LocalDate.now());
employe.setTauxHoraire(0.0);
employe.setDateCreation(LocalDateTime.now());
return employe;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Employe entity) {
return entity.getId();
}
@Override
public String createNew() {
selectedItem = new Employe();
selectedItem.setStatut("ACTIF");
selectedItem.setDateEmbauche(LocalDate.now());
return getCreatePath() + "?faces-redirect=true";
}
@lombok.Getter
@lombok.Setter
public static class Employe {
private Long id;
private String nomComplet;
private String email;
private String telephone;
private String poste;
private String statut;
private double tauxHoraire;
private LocalDate dateEmbauche;
private LocalDateTime dateCreation;
}
}

View File

@@ -0,0 +1,187 @@
package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.EquipeService;
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("equipeView")
@ViewScoped
@Getter
@Setter
public class EquipeView extends BaseListView<EquipeView.Equipe, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(EquipeView.class);
@Inject
EquipeService equipeService;
private String filtreNom;
private String filtreSpecialite;
private String filtreStatut;
private Long equipeId;
@PostConstruct
public void init() {
if (filtreStatut == null) {
filtreStatut = "TOUS";
}
loadItems();
}
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>> equipesData = equipeService.getAllEquipes();
for (Map<String, Object> data : equipesData) {
Equipe eq = new Equipe();
eq.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
eq.setNom((String) data.get("nom"));
eq.setDescription((String) data.get("description"));
eq.setSpecialite((String) data.get("specialite"));
eq.setStatut((String) data.get("statut"));
// Chef d'équipe
Object chefObj = data.get("chef");
if (chefObj instanceof Map) {
Map<String, Object> chefData = (Map<String, Object>) chefObj;
String prenom = (String) chefData.get("prenom");
String nom = (String) chefData.get("nom");
eq.setChef((prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
} else {
eq.setChef("N/A");
}
// Nombre de membres
Object membresObj = data.get("membres");
if (membresObj instanceof List) {
eq.setNombreMembres(((List<?>) membresObj).size());
} else {
eq.setNombreMembres(0);
}
if (data.get("dateCreation") != null) {
eq.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
items.add(eq);
}
LOG.info("Équipes chargées depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement équipes depuis l'API", e);
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Equipe>> buildFilters() {
List<Predicate<Equipe>> filters = new ArrayList<>();
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
filters.add(e -> e.getNom().toLowerCase().contains(filtreNom.toLowerCase()));
}
if (filtreSpecialite != null && !filtreSpecialite.trim().isEmpty()) {
filters.add(e -> e.getSpecialite() != null &&
e.getSpecialite().toLowerCase().contains(filtreSpecialite.toLowerCase()));
}
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
filters.add(e -> e.getStatut().equals(filtreStatut));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreNom = null;
filtreSpecialite = null;
filtreStatut = "TOUS";
}
@Override
protected String getDetailsPath() {
return "/equipes/";
}
@Override
protected String getCreatePath() {
return "/equipes/nouvelle";
}
@Override
protected void performDelete() {
LOG.info("Suppression équipe : {}", selectedItem.getId());
}
@Override
protected Equipe createNewEntity() {
Equipe equipe = new Equipe();
equipe.setStatut("ACTIVE");
equipe.setNombreMembres(0);
equipe.setDateCreation(LocalDateTime.now());
return equipe;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Equipe entity) {
return entity.getId();
}
@Override
public String createNew() {
selectedItem = new Equipe();
selectedItem.setStatut("ACTIVE");
return getCreatePath() + "?faces-redirect=true";
}
@lombok.Getter
@lombok.Setter
public static class Equipe {
private Long id;
private String nom;
private String description;
private String chef;
private String specialite;
private String statut;
private int nombreMembres;
private LocalDateTime dateCreation;
}
}

View File

@@ -0,0 +1,300 @@
package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.FactureService;
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("factureView")
@ViewScoped
@Getter
@Setter
public class FactureView extends BaseListView<FactureView.Facture, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(FactureView.class);
@Inject
FactureService factureService;
private String filtreNumero;
private String filtreClient;
private String filtreStatut;
private Long factureId;
@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>> facturesData = factureService.getAllFactures();
for (Map<String, Object> data : facturesData) {
Facture f = new Facture();
// Mapping des données de l'API vers l'objet Facture
f.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
f.setNumero((String) data.get("numero"));
f.setObjet((String) data.get("objet"));
// 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;
String entreprise = (String) clientData.get("entreprise");
String nom = (String) clientData.get("nom");
String prenom = (String) clientData.get("prenom");
f.setClient(entreprise != null && !entreprise.trim().isEmpty() ?
entreprise : (prenom != null ? prenom + " " : "") + (nom != null ? nom : ""));
} else if (clientObj instanceof String) {
f.setClient((String) clientObj);
} else {
f.setClient("N/A");
}
// Conversion des dates
if (data.get("dateEmission") != null) {
f.setDateEmission(LocalDate.parse(data.get("dateEmission").toString()));
}
if (data.get("dateEcheance") != null) {
f.setDateEcheance(LocalDate.parse(data.get("dateEcheance").toString()));
}
if (data.get("datePaiement") != null) {
f.setDatePaiement(LocalDate.parse(data.get("datePaiement").toString()));
}
f.setStatut((String) data.get("statut"));
// Montants
Object montantHTObj = data.get("montantHT");
if (montantHTObj != null) {
f.setMontantHT(montantHTObj instanceof Number ?
((Number) montantHTObj).doubleValue() :
Double.parseDouble(montantHTObj.toString()));
} else {
f.setMontantHT(0.0);
}
Object montantTTCObj = data.get("montantTTC");
if (montantTTCObj != null) {
f.setMontantTTC(montantTTCObj instanceof Number ?
((Number) montantTTCObj).doubleValue() :
Double.parseDouble(montantTTCObj.toString()));
} else {
f.setMontantTTC(0.0);
}
Object montantPayeObj = data.get("montantPaye");
if (montantPayeObj != null) {
f.setMontantPaye(montantPayeObj instanceof Number ?
((Number) montantPayeObj).doubleValue() :
Double.parseDouble(montantPayeObj.toString()));
} else {
f.setMontantPaye(0.0);
}
if (data.get("dateCreation") != null) {
f.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
items.add(f);
}
LOG.info("Factures chargées depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement factures depuis l'API", e);
// En cas d'erreur, on garde une liste vide
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Facture>> buildFilters() {
List<Predicate<Facture>> filters = new ArrayList<>();
if (filtreNumero != null && !filtreNumero.trim().isEmpty()) {
filters.add(f -> f.getNumero().toLowerCase().contains(filtreNumero.toLowerCase()));
}
if (filtreClient != null && !filtreClient.trim().isEmpty()) {
filters.add(f -> f.getClient().toLowerCase().contains(filtreClient.toLowerCase()));
}
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
filters.add(f -> f.getStatut().equals(filtreStatut));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreNumero = null;
filtreClient = null;
filtreStatut = "TOUS";
}
@Override
protected String getDetailsPath() {
return "/factures/";
}
@Override
protected String getCreatePath() {
return "/factures/nouvelle";
}
@Override
protected void performDelete() {
LOG.info("Suppression facture : {}", selectedItem.getId());
}
@Override
protected Facture createNewEntity() {
Facture facture = new Facture();
facture.setStatut("BROUILLON");
facture.setDateEmission(LocalDate.now());
facture.setDateEcheance(LocalDate.now().plusDays(30));
facture.setMontantHT(0.0);
facture.setMontantTTC(0.0);
facture.setMontantPaye(0.0);
facture.setDateCreation(LocalDateTime.now());
return facture;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Facture entity) {
return entity.getId();
}
/**
* Initialise une nouvelle facture pour la création.
*/
@Override
public String createNew() {
selectedItem = new Facture();
selectedItem.setStatut("BROUILLON");
selectedItem.setDateEmission(LocalDate.now());
selectedItem.setDateEcheance(LocalDate.now().plusDays(30));
return getCreatePath() + "?faces-redirect=true";
}
/**
* Sauvegarde une nouvelle facture.
*/
public String saveNew() {
if (selectedItem == null) {
selectedItem = new Facture();
}
selectedItem.setId(System.currentTimeMillis()); // Simulation ID
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Nouvelle facture créée : {}", selectedItem.getNumero());
return "/factures?faces-redirect=true";
}
/**
* Charge une facture par son ID depuis les paramètres de la requête.
*/
public void loadFactureById() {
if (factureId != null) {
loadItems(); // S'assurer que les items sont chargés
selectedItem = items.stream()
.filter(f -> f.getId().equals(factureId))
.findFirst()
.orElse(null);
if (selectedItem == null) {
LOG.warn("Facture avec ID {} non trouvé", factureId);
}
}
}
/**
* Affiche les détails d'une facture.
*/
public String viewDetails(Long id) {
selectedItem = items.stream()
.filter(f -> f.getId().equals(id))
.findFirst()
.orElse(null);
if (selectedItem != null) {
return getDetailsPath() + "details?id=" + id + "&faces-redirect=true";
}
return "/factures?faces-redirect=true";
}
/**
* Calcule le montant restant à payer.
*/
public double getMontantRestant(Facture facture) {
return facture.getMontantTTC() - facture.getMontantPaye();
}
/**
* Vérifie si une facture est en retard.
*/
public boolean isEnRetard(Facture facture) {
return facture.getDateEcheance() != null &&
facture.getDateEcheance().isBefore(LocalDate.now()) &&
!"PAYEE".equals(facture.getStatut());
}
@lombok.Getter
@lombok.Setter
public static class Facture {
private Long id;
private String numero;
private String objet;
private String client;
private LocalDate dateEmission;
private LocalDate dateEcheance;
private LocalDate datePaiement;
private String statut;
private double montantHT;
private double montantTTC;
private double montantPaye;
private LocalDateTime dateCreation;
}
}

View File

@@ -0,0 +1,189 @@
package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.MaterielService;
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("materielView")
@ViewScoped
@Getter
@Setter
public class MaterielView extends BaseListView<MaterielView.Materiel, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(MaterielView.class);
@Inject
MaterielService materielService;
private String filtreNom;
private String filtreType;
private String filtreStatut;
private Long materielId;
@PostConstruct
public void init() {
if (filtreStatut == null) {
filtreStatut = "TOUS";
}
loadItems();
}
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>> materielsData = materielService.getAllMateriels();
for (Map<String, Object> data : materielsData) {
Materiel m = new Materiel();
m.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
m.setNom((String) data.get("nom"));
m.setMarque((String) data.get("marque"));
m.setModele((String) data.get("modele"));
m.setNumeroSerie((String) data.get("numeroSerie"));
m.setType((String) data.get("type"));
m.setStatut((String) data.get("statut"));
// Valeur d'achat
Object valeurObj = data.get("valeurAchat");
if (valeurObj != null) {
m.setValeurAchat(valeurObj instanceof Number ?
((Number) valeurObj).doubleValue() :
Double.parseDouble(valeurObj.toString()));
} else {
m.setValeurAchat(0.0);
}
// Date d'achat
if (data.get("dateAchat") != null) {
m.setDateAchat(LocalDate.parse(data.get("dateAchat").toString()));
}
if (data.get("dateCreation") != null) {
m.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
items.add(m);
}
LOG.info("Matériels chargés depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement matériels depuis l'API", e);
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Materiel>> buildFilters() {
List<Predicate<Materiel>> filters = new ArrayList<>();
if (filtreNom != null && !filtreNom.trim().isEmpty()) {
filters.add(m -> m.getNom().toLowerCase().contains(filtreNom.toLowerCase()));
}
if (filtreType != null && !filtreType.trim().isEmpty() && !"TOUS".equals(filtreType)) {
filters.add(m -> m.getType() != null && m.getType().equals(filtreType));
}
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
filters.add(m -> m.getStatut().equals(filtreStatut));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreNom = null;
filtreType = "TOUS";
filtreStatut = "TOUS";
}
@Override
protected String getDetailsPath() {
return "/materiels/";
}
@Override
protected String getCreatePath() {
return "/materiels/nouveau";
}
@Override
protected void performDelete() {
LOG.info("Suppression matériel : {}", selectedItem.getId());
}
@Override
protected Materiel createNewEntity() {
Materiel materiel = new Materiel();
materiel.setStatut("DISPONIBLE");
materiel.setDateAchat(LocalDate.now());
materiel.setValeurAchat(0.0);
materiel.setDateCreation(LocalDateTime.now());
return materiel;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Materiel entity) {
return entity.getId();
}
@Override
public String createNew() {
selectedItem = new Materiel();
selectedItem.setStatut("DISPONIBLE");
selectedItem.setDateAchat(LocalDate.now());
return getCreatePath() + "?faces-redirect=true";
}
@lombok.Getter
@lombok.Setter
public static class Materiel {
private Long id;
private String nom;
private String marque;
private String modele;
private String numeroSerie;
private String type;
private String statut;
private double valeurAchat;
private LocalDate dateAchat;
private LocalDateTime dateCreation;
}
}

View File

@@ -0,0 +1,223 @@
package dev.lions.btpxpress.view;
import dev.lions.btpxpress.service.StockService;
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("stockView")
@ViewScoped
@Getter
@Setter
public class StockView extends BaseListView<StockView.Stock, Long> implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(StockView.class);
@Inject
StockService stockService;
private String filtreReference;
private String filtreDesignation;
private String filtreCategorie;
private String filtreStatut;
private Long stockId;
@PostConstruct
public void init() {
if (filtreStatut == null) {
filtreStatut = "TOUS";
}
if (filtreCategorie == null) {
filtreCategorie = "TOUS";
}
loadItems();
}
public void setFiltreStatut(String statut) {
this.filtreStatut = statut;
}
public void setFiltreCategorie(String categorie) {
this.filtreCategorie = categorie;
}
@Override
public void loadItems() {
loading = true;
try {
items = new ArrayList<>();
// Récupération depuis l'API backend
List<Map<String, Object>> stocksData = stockService.getAllStocks();
for (Map<String, Object> data : stocksData) {
Stock s = new Stock();
s.setId(data.get("id") != null ? Long.valueOf(data.get("id").toString().hashCode()) : null);
s.setReference((String) data.get("reference"));
s.setDesignation((String) data.get("designation"));
s.setCategorie((String) data.get("categorie"));
s.setUniteMesure((String) data.get("uniteMesure"));
s.setStatut((String) data.get("statut"));
// Quantité disponible
Object qteObj = data.get("quantiteDisponible");
if (qteObj != null) {
s.setQuantiteDisponible(qteObj instanceof Number ?
((Number) qteObj).doubleValue() :
Double.parseDouble(qteObj.toString()));
} else {
s.setQuantiteDisponible(0.0);
}
// Seuil d'alerte
Object seuilObj = data.get("seuilAlerte");
if (seuilObj != null) {
s.setSeuilAlerte(seuilObj instanceof Number ?
((Number) seuilObj).doubleValue() :
Double.parseDouble(seuilObj.toString()));
} else {
s.setSeuilAlerte(0.0);
}
// Prix unitaire
Object prixObj = data.get("prixUnitaire");
if (prixObj != null) {
s.setPrixUnitaire(prixObj instanceof Number ?
((Number) prixObj).doubleValue() :
Double.parseDouble(prixObj.toString()));
} else {
s.setPrixUnitaire(0.0);
}
if (data.get("dateCreation") != null) {
s.setDateCreation(LocalDateTime.parse(data.get("dateCreation").toString()));
}
items.add(s);
}
LOG.info("Stocks chargés depuis l'API : {} élément(s)", items.size());
applyFilters(items, buildFilters());
} catch (Exception e) {
LOG.error("Erreur chargement stocks depuis l'API", e);
items = new ArrayList<>();
} finally {
loading = false;
}
}
private List<Predicate<Stock>> buildFilters() {
List<Predicate<Stock>> filters = new ArrayList<>();
if (filtreReference != null && !filtreReference.trim().isEmpty()) {
filters.add(s -> s.getReference() != null &&
s.getReference().toLowerCase().contains(filtreReference.toLowerCase()));
}
if (filtreDesignation != null && !filtreDesignation.trim().isEmpty()) {
filters.add(s -> s.getDesignation() != null &&
s.getDesignation().toLowerCase().contains(filtreDesignation.toLowerCase()));
}
if (filtreCategorie != null && !filtreCategorie.trim().isEmpty() && !"TOUS".equals(filtreCategorie)) {
filters.add(s -> s.getCategorie() != null && s.getCategorie().equals(filtreCategorie));
}
if (filtreStatut != null && !filtreStatut.trim().isEmpty() && !"TOUS".equals(filtreStatut)) {
filters.add(s -> s.getStatut() != null && s.getStatut().equals(filtreStatut));
}
return filters;
}
@Override
protected void resetFilterFields() {
filtreReference = null;
filtreDesignation = null;
filtreCategorie = "TOUS";
filtreStatut = "TOUS";
}
@Override
protected String getDetailsPath() {
return "/stock/";
}
@Override
protected String getCreatePath() {
return "/stock/nouveau";
}
@Override
protected void performDelete() {
LOG.info("Suppression stock : {}", selectedItem.getId());
}
@Override
protected Stock createNewEntity() {
Stock stock = new Stock();
stock.setStatut("DISPONIBLE");
stock.setQuantiteDisponible(0.0);
stock.setSeuilAlerte(0.0);
stock.setPrixUnitaire(0.0);
stock.setDateCreation(LocalDateTime.now());
return stock;
}
@Override
protected void performCreate() {
if (selectedItem.getId() == null) {
selectedItem.setId(System.currentTimeMillis());
}
selectedItem.setDateCreation(LocalDateTime.now());
items.add(selectedItem);
LOG.info("Created: {}", selectedItem);
}
@Override
protected void performUpdate() {
LOG.info("Updated: {}", selectedItem);
}
@Override
protected Long getEntityId(Stock entity) {
return entity.getId();
}
@Override
public String createNew() {
selectedItem = new Stock();
selectedItem.setStatut("DISPONIBLE");
return getCreatePath() + "?faces-redirect=true";
}
/**
* Vérifie si un stock est en alerte (quantité < seuil)
*/
public boolean isEnAlerte(Stock stock) {
return stock.getQuantiteDisponible() < stock.getSeuilAlerte();
}
@lombok.Getter
@lombok.Setter
public static class Stock {
private Long id;
private String reference;
private String designation;
private String categorie;
private String uniteMesure;
private String statut;
private double quantiteDisponible;
private double seuilAlerte;
private double prixUnitaire;
private LocalDateTime dateCreation;
}
}