Checkpoint: Checkpoint saved: Ajout de la fonctionnalité d'export intelligent des factures BAP
Nouvelle fonctionnalité implémentée : ✅ Sélection automatique des factures exportables uniquement ✅ Critères d'exportabilité renforcés : Score 100% + Type d'achat + Service + Ventilation remplis ✅ Case à cocher générale qui sélectionne toutes les factures exportables ✅ Cases à cocher individuelles désactivées pour les factures non exportables ✅ Bouton "Exporter" actif dès qu'au moins une facture est cochée ✅ Tooltips explicatifs sur les cases à cocher Modifications techniques : - client/src/pages/InvoicesBAP.tsx : Fonction isEligibleForExport modifiée avec critères complets - client/src/pages/InvoicesBAP.tsx : handleSelectAll modifié pour sélectionner uniquement les factures exportables - client/src/pages/InvoicesBAP.tsx : Ajout de l'attribut disabled sur les checkboxes non exportables - client/src/pages/InvoicesBAP.tsx : Ajout de tooltips explicatifs - client/src/pages/InvoicesBAP.tsx : Simplification du bouton "Exporter" (activation dès qu'une facture est cochée) Fonctionnement : 1. Une facture BAP est exportable si et seulement si : - Score de qualité = 100% - Type d'achat rempli (CAPEX ou OPEX) - Service concerné rempli - Ventilation comptable remplie 2. La case à cocher générale (en-tête du tableau) sélectionne automatiquement toutes les factures qui remplissent ces critères 3. Les cases à cocher individuelles sont : - Actives pour les factures exportables - Désactivées (grisées) pour les factures non exportables - Avec tooltip explicatif sur les cases désactivées 4. Le bouton "Exporter" s'active dès qu'au moins une facture est cochée et affiche le nombre de factures sélectionnées Cette fonctionnalité garantit que seules les factures complètes et validées peuvent être exportées, évitant ainsi les erreurs d'export de données incomplètes.
This commit is contained in:
@@ -236,9 +236,11 @@ export default function InvoicesBAP() {
|
|||||||
|
|
||||||
const handleSelectAll = (checked: boolean) => {
|
const handleSelectAll = (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
// Select all invoices (no restriction)
|
// Select only exportable invoices
|
||||||
const allIds = (filteredInvoices || []).map(inv => inv.id);
|
const exportableIds = (filteredInvoices || [])
|
||||||
setSelectedIds(allIds);
|
.filter(inv => isEligibleForExport(inv))
|
||||||
|
.map(inv => inv.id);
|
||||||
|
setSelectedIds(exportableIds);
|
||||||
} else {
|
} else {
|
||||||
setSelectedIds([]);
|
setSelectedIds([]);
|
||||||
}
|
}
|
||||||
@@ -291,7 +293,17 @@ export default function InvoicesBAP() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isEligibleForExport = (invoice: any) => {
|
const isEligibleForExport = (invoice: any) => {
|
||||||
return (invoice.qualityScore || 0) === 100;
|
// Une facture BAP est exportable si :
|
||||||
|
// 1. Score = 100%
|
||||||
|
// 2. Type d'achat rempli
|
||||||
|
// 3. Service concerné rempli
|
||||||
|
// 4. Ventilation comptable remplie
|
||||||
|
return (
|
||||||
|
(invoice.qualityScore || 0) === 100 &&
|
||||||
|
invoice.typeAchat &&
|
||||||
|
invoice.serviceConcerne &&
|
||||||
|
invoice.ventilationComptable
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasOnlyEligibleSelected = selectedIds.length > 0 && selectedIds.every(id => {
|
const hasOnlyEligibleSelected = selectedIds.length > 0 && selectedIds.every(id => {
|
||||||
@@ -322,12 +334,11 @@ export default function InvoicesBAP() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleExport}
|
onClick={handleExport}
|
||||||
disabled={selectedIds.length === 0 || !hasOnlyEligibleSelected || exportMutation.isPending}
|
disabled={selectedIds.length === 0 || exportMutation.isPending}
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
className="bg-blue-600 hover:bg-blue-700"
|
||||||
title={!hasOnlyEligibleSelected && selectedIds.length > 0 ? "Seules les factures avec un score de 100% peuvent être exportées" : ""}
|
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4 mr-2" />
|
<Download className="w-4 h-4 mr-2" />
|
||||||
PDF ({selectedIds.length})
|
Exporter ({selectedIds.length})
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -419,6 +430,7 @@ export default function InvoicesBAP() {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={allEligibleSelected}
|
checked={allEligibleSelected}
|
||||||
onCheckedChange={handleSelectAll}
|
onCheckedChange={handleSelectAll}
|
||||||
|
title="Sélectionner toutes les factures exportables (Score 100% + champs remplis)"
|
||||||
/>
|
/>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableHead>Fournisseur</TableHead>
|
<TableHead>Fournisseur</TableHead>
|
||||||
@@ -442,6 +454,8 @@ export default function InvoicesBAP() {
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedIds.includes(invoice.id)}
|
checked={selectedIds.includes(invoice.id)}
|
||||||
onCheckedChange={(checked) => handleSelectOne(invoice.id, checked as boolean)}
|
onCheckedChange={(checked) => handleSelectOne(invoice.id, checked as boolean)}
|
||||||
|
disabled={!isEligibleForExport(invoice)}
|
||||||
|
title={!isEligibleForExport(invoice) ? "Facture non exportable (score < 100% ou champs manquants)" : ""}
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="font-medium">
|
<TableCell className="font-medium">
|
||||||
|
|||||||
59
todo.md
59
todo.md
@@ -430,5 +430,60 @@
|
|||||||
|
|
||||||
## Correction bug accordéon
|
## Correction bug accordéon
|
||||||
- [x] Empêcher la fermeture de l'accordéon lors du clic sur un sous-menu
|
- [x] Empêcher la fermeture de l'accordéon lors du clic sur un sous-menu
|
||||||
- [ ] Tester le comportement après correction
|
- [x] Tester le comportement après correction
|
||||||
- [ ] Déployer sur le VPS
|
- [x] Déployer sur le VPS
|
||||||
|
|
||||||
|
## Configuration HTTPS sur le VPS
|
||||||
|
- [x] Générer un certificat SSL auto-signé sur le VPS
|
||||||
|
- [x] Créer le répertoire /etc/nginx/ssl pour stocker les certificats
|
||||||
|
- [x] Configurer Nginx pour écouter sur le port 443 (HTTPS)
|
||||||
|
- [x] Ajouter la redirection HTTP vers HTTPS
|
||||||
|
- [x] Configurer les en-têtes de sécurité SSL
|
||||||
|
- [x] Redémarrer Nginx
|
||||||
|
- [x] Tester l'accès HTTPS via https://20.56.92.59
|
||||||
|
- [x] Vérifier la redirection HTTP vers HTTPS
|
||||||
|
|
||||||
|
## Configuration sécurité VPS (UFW + Fail2Ban)
|
||||||
|
- [x] Vérifier l'état actuel du pare-feu UFW
|
||||||
|
- [x] Configurer UFW pour autoriser SSH (port 22)
|
||||||
|
- [x] Configurer UFW pour autoriser HTTP (port 80)
|
||||||
|
- [x] Configurer UFW pour autoriser HTTPS (port 443)
|
||||||
|
- [x] Activer le pare-feu UFW
|
||||||
|
- [x] Installer Fail2Ban
|
||||||
|
- [x] Configurer Fail2Ban pour SSH (maxretry=3, bantime=3600)
|
||||||
|
- [x] Créer une liste blanche d'IPs de confiance
|
||||||
|
- [x] Démarrer et activer Fail2Ban
|
||||||
|
- [x] Tester la configuration et vérifier les logs
|
||||||
|
|
||||||
|
## Ajout des IPs de confiance à la liste blanche Fail2Ban
|
||||||
|
- [x] Mettre à jour /etc/fail2ban/jail.local avec les IPs 185.61.190.35 et 197.221.123.242
|
||||||
|
- [x] Redémarrer Fail2Ban pour appliquer les modifications
|
||||||
|
- [x] Vérifier que les IPs sont bien dans la liste blanche
|
||||||
|
|
||||||
|
## Configuration Let's Encrypt pour facturation.itinova.org
|
||||||
|
- [x] Vérifier la configuration DNS (facturation.itinova.org → 20.56.92.59)
|
||||||
|
- [x] Installer Certbot (client Let's Encrypt)
|
||||||
|
- [x] Obtenir le certificat SSL pour facturation.itinova.org
|
||||||
|
- [x] Configurer Nginx pour utiliser le certificat Let's Encrypt
|
||||||
|
- [x] Tester l'accès HTTPS via https://facturation.itinova.org
|
||||||
|
- [x] Configurer le renouvellement automatique du certificat
|
||||||
|
- [x] Vérifier que la redirection HTTP → HTTPS fonctionne
|
||||||
|
|
||||||
|
## Correction configuration Nginx pour facturation.itinova.org
|
||||||
|
- [x] Vérifier la configuration Nginx actuelle
|
||||||
|
- [x] Identifier pourquoi facturation.itinova.org affiche la page par défaut
|
||||||
|
- [x] Créer une configuration Nginx spécifique pour facturation.itinova.org pointant vers l'application Node.js
|
||||||
|
- [x] Tester la configuration Nginx
|
||||||
|
- [x] Redémarrer Nginx
|
||||||
|
- [x] Vérifier que https://facturation.itinova.org affiche l'application
|
||||||
|
|
||||||
|
## Export des factures BAP
|
||||||
|
- [x] Définir les critères d'exportabilité : Score 100% + Type d'achat rempli + Service rempli + Ventilation remplie
|
||||||
|
- [x] Ajouter fonction isExportable dans InvoicesBAP.tsx
|
||||||
|
- [x] Ajouter colonne avec cases à cocher pour sélection des factures exportables
|
||||||
|
- [x] Ajouter case à cocher générale qui sélectionne toutes les factures exportables
|
||||||
|
- [x] Désactiver les cases à cocher pour les factures non exportables
|
||||||
|
- [x] Ajouter bouton "Exporter" qui s'active si au moins une facture est cochée
|
||||||
|
- [x] Utiliser la route tRPC existante pour exporter les factures sélectionnées
|
||||||
|
- [x] Implémenter la génération de fichiers d'export (PDF)
|
||||||
|
- [ ] Tester la sélection et l'export des factures BAP
|
||||||
|
|||||||
Reference in New Issue
Block a user