'use client'; import React, { useState, useEffect, useRef } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { Card } from 'primereact/card'; import { Button } from 'primereact/button'; import { InputText } from 'primereact/inputtext'; import { Calendar } from 'primereact/calendar'; import { Dropdown } from 'primereact/dropdown'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Toast } from 'primereact/toast'; import { ProgressSpinner } from 'primereact/progressspinner'; import { Toolbar } from 'primereact/toolbar'; import { Message } from 'primereact/message'; import { Checkbox } from 'primereact/checkbox'; import { InputNumber } from 'primereact/inputnumber'; import { devisService, factureService } from '../../../../../services/api'; import { formatCurrency, formatDate } from '../../../../../utils/formatters'; import type { Devis, Facture } from '../../../../../types/btp'; import { StatutFacture, TypeFacture } from '../../../../../types/btp'; const ConvertDevisPage = () => { const params = useParams(); const router = useRouter(); const toast = useRef(null); const [devis, setDevis] = useState(null); const [facture, setFacture] = useState>({ numero: '', dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), objet: '', description: '', montantHT: 0, montantTTC: 0, tauxTVA: 20, statut: StatutFacture.BROUILLON, typeFacture: TypeFacture.FACTURE, actif: true, lignes: [] }); const [loading, setLoading] = useState(true); const [converting, setConverting] = useState(false); const [selectedLignes, setSelectedLignes] = useState([]); const [acomptePercentage, setAcomptePercentage] = useState(0); const [createAcompte, setCreateAcompte] = useState(false); const devisId = params.id as string; const typeFactureOptions = [ { label: 'Facture', value: 'FACTURE' }, { label: 'Facture d\'acompte', value: 'ACOMPTE' }, { label: 'Facture de situation', value: 'SITUATION' }, { label: 'Facture de solde', value: 'SOLDE' } ]; useEffect(() => { loadDevis(); }, [devisId]); const loadDevis = async () => { try { setLoading(true); const devisData = await devisService.getById(devisId); if (devisData.statut !== 'ACCEPTE') { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Seuls les devis acceptés peuvent être convertis en facture' }); } setDevis(devisData); // Générer un nouveau numéro de facture const newNumero = await generateNewNumero(); // Initialiser la facture avec les données du devis setFacture({ numero: newNumero, dateEmission: new Date().toISOString(), dateEcheance: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), objet: devisData.objet, description: devisData.description, montantHT: devisData.montantHT, montantTTC: devisData.montantTTC, tauxTVA: devisData.tauxTVA, statut: StatutFacture.BROUILLON, typeFacture: TypeFacture.FACTURE, actif: true, client: devisData.client }); // Initialiser toutes les lignes comme sélectionnées setSelectedLignes(new Array(devisData.lignes?.length || 0).fill(true)); } catch (error) { console.error('Erreur lors du chargement:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger le devis' }); } finally { setLoading(false); } }; const generateNewNumero = async (): Promise => { try { // TODO: Implémenter la génération automatique de numéro const year = new Date().getFullYear(); const timestamp = Date.now().toString().slice(-6); return `FAC-${year}-${timestamp}`; } catch (error) { console.error('Erreur génération numéro:', error); return `FAC-${Date.now()}`; } }; const calculateMontants = () => { if (!facture.lignes) return; const lignesSelectionnees = facture.lignes?.filter((_, index) => selectedLignes[index]) || []; let montantHT = lignesSelectionnees.reduce((sum, ligne) => sum + (ligne.montantLigne || 0), 0); // Appliquer le pourcentage d'acompte si nécessaire if (createAcompte && acomptePercentage > 0) { montantHT = montantHT * (acomptePercentage / 100); } const montantTTC = montantHT * (1 + (facture.tauxTVA || 0) / 100); setFacture(prev => ({ ...prev, montantHT, montantTTC })); }; useEffect(() => { calculateMontants(); }, [selectedLignes, facture.lignes, facture.tauxTVA, createAcompte, acomptePercentage]); const handleConvert = async () => { try { setConverting(true); // Validation if (!facture.numero || !facture.objet) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez remplir tous les champs obligatoires' }); return; } if (!selectedLignes.some(selected => selected)) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez sélectionner au moins une ligne à facturer' }); return; } // Préparer les lignes sélectionnées const lignesFacture = facture.lignes?.filter((_, index) => selectedLignes[index]) || []; // Ajuster les montants si c'est un acompte if (createAcompte && acomptePercentage > 0) { lignesFacture.forEach(ligne => { ligne.montantLigne = (ligne.montantLigne || 0) * (acomptePercentage / 100); ligne.prixUnitaire = ligne.montantLigne / (ligne.quantite || 1); }); } const factureData = { ...facture, lignes: lignesFacture, typeFacture: createAcompte ? TypeFacture.ACOMPTE : facture.typeFacture }; const createdFacture = await factureService.create(factureData); toast.current?.show({ severity: 'success', summary: 'Succès', detail: 'Facture créée avec succès' }); router.push(`/factures/${createdFacture.id}`); } catch (error) { console.error('Erreur lors de la conversion:', error); toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Erreur lors de la conversion en facture' }); } finally { setConverting(false); } }; const handleLigneSelection = (index: number, checked: boolean) => { const newSelection = [...selectedLignes]; newSelection[index] = checked; setSelectedLignes(newSelection); }; const handleSelectAll = (checked: boolean) => { setSelectedLignes(new Array(facture.lignes?.length || 0).fill(checked)); }; const toolbarStartTemplate = () => (
); const toolbarEndTemplate = () => (
); const selectionBodyTemplate = (rowData: any, options: any) => ( handleLigneSelection(options.rowIndex, e.checked || false)} /> ); if (loading) { return (
); } if (!devis) { return (

Devis introuvable

Le devis à convertir n'existe pas

); } return (
{/* Message d'information */}
{devis.statut !== 'ACCEPTE' ? ( ) : ( )}
{/* Informations du devis source */}

{devis.numero}

{devis.objet}

{typeof devis.client === 'string' ? devis.client : devis.client?.nom}

{formatDate(devis.dateEmission)}

{formatCurrency(devis.montantTTC)}

{/* Informations de la facture */}
setFacture(prev => ({ ...prev, numero: e.target.value }))} className="w-full" required />
setFacture(prev => ({ ...prev, dateEmission: e.value?.toISOString() || new Date().toISOString() }))} className="w-full" dateFormat="dd/mm/yy" />
setFacture(prev => ({ ...prev, dateEcheance: e.value || new Date() }))} className="w-full" dateFormat="dd/mm/yy" />
setFacture(prev => ({ ...prev, typeFacture: e.value }))} className="w-full" />

{formatCurrency(facture.montantTTC || 0)}

{/* Options d'acompte */}
setCreateAcompte(e.checked || false)} />
{createAcompte && (
setAcomptePercentage(e.value || 0)} className="w-full md:w-3" suffix="%" min={0} max={100} /> Le montant de la facture sera calculé en fonction de ce pourcentage
)}
{/* Sélection des prestations */}
s).length}/${facture.lignes?.length || 0} sélectionnée(s) | Total: ${formatCurrency(facture.montantTTC || 0)}`} >
rowData.quantite?.toLocaleString('fr-FR')} /> formatCurrency(rowData.prixUnitaire)} /> { let montant = rowData.montantHT; if (createAcompte && acomptePercentage > 0 && selectedLignes[options.rowIndex]) { montant = montant * (acomptePercentage / 100); } return formatCurrency(montant); }} />
Total HT: {formatCurrency(facture.montantHT || 0)} Total TTC: {formatCurrency(facture.montantTTC || 0)}
{createAcompte && acomptePercentage > 0 && (

Acompte de {acomptePercentage}% appliqué sur les prestations sélectionnées

)}
); }; export default ConvertDevisPage;