Files
btpxpress-frontend/target/classes/META-INF/resources/devis/nouveau.xhtml
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

313 lines
22 KiB
HTML

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
template="/WEB-INF/template.xhtml">
<ui:define name="title">Nouveau devis - BTP Xpress</ui:define>
<ui:define name="content">
<div class="layout-dashboard">
<div class="grid">
<div class="col-12">
<div class="card">
<!-- En-tête avec breadcrumb -->
<div class="flex align-items-center justify-content-between mb-4">
<div>
<h2 class="text-900 font-bold mb-2">Créer un nouveau devis</h2>
<p class="text-600 mt-0">Établissez un devis détaillé pour votre client</p>
</div>
<p:commandButton value="Retour à la liste"
icon="pi pi-arrow-left"
outcome="/devis"
styleClass="ui-button-secondary ui-button-outlined"/>
</div>
<p:messages id="messages" showDetail="true" closable="true"/>
<h:form id="nouveauDevisForm" styleClass="p-fluid">
<!-- SECTION 1: Informations générales -->
<p:panel header="Informations générales" toggleable="true" collapsed="false" class="mb-4">
<div class="formgrid grid">
<!-- Numéro (auto-généré) -->
<div class="field col-12 md:col-4">
<label for="numero" class="font-bold">Numéro de devis</label>
<div class="p-inputgroup">
<span class="p-inputgroup-addon">
<i class="pi pi-hashtag"></i>
</span>
<p:inputText id="numero"
value="#{devisView.entity.numero}"
disabled="true"
placeholder="Auto-généré"
styleClass="text-center font-bold"/>
</div>
<small class="text-600">Généré automatiquement lors de l'enregistrement</small>
</div>
<!-- Statut -->
<div class="field col-12 md:col-4">
<label for="statut" class="font-bold">Statut <span class="text-red-500">*</span></label>
<p:selectOneMenu id="statut"
value="#{devisView.entity.statut}"
required="true">
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true"/>
<f:selectItem itemLabel="Brouillon" itemValue="BROUILLON"/>
<f:selectItem itemLabel="En attente" itemValue="ATTENTE"/>
<f:selectItem itemLabel="Accepté" itemValue="ACCEPTE"/>
<f:selectItem itemLabel="Refusé" itemValue="REFUSE"/>
<f:selectItem itemLabel="Expiré" itemValue="EXPIRE"/>
</p:selectOneMenu>
</div>
<!-- Date d'émission -->
<div class="field col-12 md:col-4">
<label for="dateEmission" class="font-bold">Date d'émission <span class="text-red-500">*</span></label>
<p:calendar id="dateEmission"
value="#{devisView.entity.dateEmission}"
pattern="dd/MM/yyyy"
locale="fr"
required="true"
requiredMessage="La date d'émission est obligatoire"
showIcon="true"
showButtonBar="true"
monthNavigator="true"
yearNavigator="true"
yearRange="2020:2030"
placeholder="Sélectionner une date">
</p:calendar>
</div>
<!-- Client -->
<div class="field col-12 md:col-8">
<label for="client" class="font-bold">Client <span class="text-red-500">*</span></label>
<p:inputText id="client"
value="#{devisView.entity.client}"
required="true"
requiredMessage="Le client est obligatoire"
placeholder="Ex: Entreprise ABC SARL">
<f:validateLength minimum="2" maximum="200"/>
</p:inputText>
<small class="text-600">Nom du client ou de l'entreprise</small>
</div>
<!-- Date de validité -->
<div class="field col-12 md:col-4">
<label for="dateValidite" class="font-bold">Date de validité <span class="text-red-500">*</span></label>
<p:calendar id="dateValidite"
value="#{devisView.entity.dateValidite}"
pattern="dd/MM/yyyy"
locale="fr"
required="true"
requiredMessage="La date de validité est obligatoire"
showIcon="true"
showButtonBar="true"
monthNavigator="true"
yearNavigator="true"
yearRange="2020:2035"
mindate="#{devisView.entity.dateEmission}"
placeholder="Sélectionner une date">
</p:calendar>
<small class="text-600">Date limite de validité du devis (généralement 30 jours)</small>
</div>
<!-- Objet du devis -->
<div class="field col-12">
<label for="objet" class="font-bold">Objet du devis <span class="text-red-500">*</span></label>
<p:inputTextarea id="objet"
value="#{devisView.entity.objet}"
required="true"
requiredMessage="L'objet du devis est obligatoire"
rows="3"
placeholder="Ex: Construction d'un immeuble R+3 à usage résidentiel"
autoResize="false">
<f:validateLength minimum="10" maximum="500"/>
</p:inputTextarea>
<small class="text-600">Description détaillée de la prestation</small>
</div>
</div>
</p:panel>
<!-- SECTION 2: Lignes du devis -->
<p:panel header="Détail du devis" toggleable="true" collapsed="false" class="mb-4">
<div class="mb-3">
<div class="surface-100 border-round p-3">
<div class="flex align-items-center gap-2 mb-2">
<i class="pi pi-info-circle text-blue-500"></i>
<span class="text-900 font-medium">Lignes de devis</span>
</div>
<p class="text-600 text-sm mt-0 mb-0">
Ajoutez les différentes prestations, fournitures et main d'œuvre.
Cette fonctionnalité sera disponible dans une prochaine version.
</p>
</div>
</div>
<!-- Placeholder pour table de lignes -->
<div class="surface-50 border-round p-4 text-center">
<i class="pi pi-list text-400 mb-3" style="font-size: 3rem;"></i>
<p class="text-600 mt-0 mb-3">Gestion des lignes de devis en cours de développement</p>
<p class="text-500 text-sm">
Bientôt disponible: ajout de lignes avec désignation, quantité, prix unitaire, TVA, etc.
</p>
</div>
</p:panel>
<!-- SECTION 3: Montants et totaux -->
<p:panel header="Montants" toggleable="true" collapsed="false" class="mb-4">
<div class="formgrid grid">
<!-- Montant HT -->
<div class="field col-12 md:col-6">
<label for="montantHT" class="font-bold">Montant HT (FCFA) <span class="text-red-500">*</span></label>
<p:inputNumber id="montantHT"
value="#{devisView.entity.montantHT}"
required="true"
requiredMessage="Le montant HT est obligatoire"
minValue="0"
decimalPlaces="0"
thousandSeparator=" "
suffix=" FCFA"
placeholder="0">
</p:inputNumber>
<small class="text-600">Montant hors taxes</small>
</div>
<!-- TVA (calculée) -->
<div class="field col-12 md:col-6">
<label class="font-bold">TVA (18%)</label>
<div class="p-inputgroup">
<span class="p-inputgroup-addon">
<i class="pi pi-percentage"></i>
</span>
<p:inputNumber value="#{devisView.entity.montantHT * 0.18}"
disabled="true"
decimalPlaces="0"
thousandSeparator=" "
suffix=" FCFA"
styleClass="text-center font-medium"/>
</div>
<small class="text-600">Calculé automatiquement (18% du montant HT)</small>
</div>
<!-- Montant TTC (calculé) -->
<div class="field col-12">
<label class="font-bold">Montant TTC (FCFA)</label>
<div class="p-inputgroup">
<span class="p-inputgroup-addon bg-primary">
<i class="pi pi-dollar text-white"></i>
</span>
<p:inputNumber value="#{devisView.entity.montantHT * 1.18}"
disabled="true"
decimalPlaces="0"
thousandSeparator=" "
suffix=" FCFA"
styleClass="text-center font-bold text-xl text-primary"/>
</div>
<small class="text-600">Montant toutes taxes comprises (HT + TVA)</small>
</div>
<!-- Récapitulatif visuel -->
<div class="field col-12">
<div class="surface-100 border-round p-3">
<div class="grid">
<div class="col-12 md:col-4">
<div class="text-center">
<span class="text-600 text-sm block mb-2">Montant HT</span>
<div class="text-900 font-bold text-xl">
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
<ui:param name="amount" value="#{devisView.entity.montantHT}"/>
<ui:param name="size" value="normal"/>
</ui:include>
</div>
</div>
</div>
<div class="col-12 md:col-4">
<div class="text-center">
<span class="text-600 text-sm block mb-2">TVA (18%)</span>
<div class="text-orange-600 font-bold text-xl">
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
<ui:param name="amount" value="#{devisView.entity.montantHT * 0.18}"/>
<ui:param name="size" value="normal"/>
</ui:include>
</div>
</div>
</div>
<div class="col-12 md:col-4">
<div class="text-center">
<span class="text-600 text-sm block mb-2">Total TTC</span>
<div class="text-primary font-bold text-2xl">
<ui:include src="/WEB-INF/components/monetary-display.xhtml">
<ui:param name="amount" value="#{devisView.entity.montantHT * 1.18}"/>
<ui:param name="size" value="large"/>
<ui:param name="bold" value="true"/>
</ui:include>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</p:panel>
<!-- SECTION 4: Conditions (optionnel) -->
<p:panel header="Conditions et remarques" toggleable="true" collapsed="true" class="mb-4">
<div class="formgrid grid">
<div class="field col-12">
<label for="conditions" class="font-bold">Conditions de paiement</label>
<p:inputTextarea id="conditions"
rows="3"
placeholder="Ex: Paiement en 3 fois : 30% à la commande, 40% à mi-parcours, 30% à la livraison"
autoResize="false">
</p:inputTextarea>
<small class="text-600">Détaillez les modalités de paiement</small>
</div>
<div class="field col-12">
<label for="remarques" class="font-bold">Remarques</label>
<p:inputTextarea id="remarques"
rows="3"
placeholder="Toutes remarques ou précisions supplémentaires"
autoResize="false">
</p:inputTextarea>
</div>
</div>
</p:panel>
<!-- Boutons d'action -->
<div class="flex align-items-center justify-content-between pt-4 border-top-1 surface-border">
<div>
<span class="text-600 text-sm">Les champs marqués d'un </span>
<span class="text-red-500 font-bold">*</span>
<span class="text-600 text-sm"> sont obligatoires</span>
</div>
<div class="flex gap-2">
<p:commandButton value="Annuler"
icon="pi pi-times"
action="/devis?faces-redirect=true"
styleClass="ui-button-secondary"
immediate="true"/>
<p:commandButton value="Enregistrer comme brouillon"
icon="pi pi-save"
action="#{devisView.save}"
update="@form messages"
oncomplete="if (args &amp;&amp; !args.validationFailed) window.location.href='/devis.xhtml';"
styleClass="ui-button-secondary"/>
<p:commandButton value="Enregistrer et envoyer"
icon="pi pi-send"
action="#{devisView.save}"
update="@form messages"
oncomplete="if (args &amp;&amp; !args.validationFailed) window.location.href='/devis.xhtml';"
styleClass="ui-button-primary"/>
</div>
</div>
</h:form>
</div>
</div>
</div>
</div>
</ui:define>
</ui:composition>