mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-11 17:27:31 +02:00
3 files changed
+321
-111
No files matched your search
@@ -0,0 +1,154 @@
|
||||
# Ajout Colonne Écart - Module Rapprochement
|
||||
|
||||
## 🎯 Demande Utilisateur
|
||||
|
||||
Ajouter une colonne **"Écart"** dans le module rapprochement qui calcule la différence entre le montant BL et le montant Facture.
|
||||
|
||||
## ✅ Implementation
|
||||
|
||||
### 1. **Structure des Tableaux**
|
||||
|
||||
#### **Onglet Manuel**
|
||||
```
|
||||
| Fournisseur | N° BL | Date Livr. | Montant BL | Ref. Facture | Montant Fact. | **Écart** | Magasin | Actions |
|
||||
```
|
||||
|
||||
#### **Onglet Automatique**
|
||||
```
|
||||
| Fournisseur | N° BL | Date Livr. | Date Valid. | Montant BL | Ref. Facture | Montant Fact. | **Écart** | Magasin | Actions |
|
||||
```
|
||||
|
||||
### 2. **Logique de Calcul**
|
||||
|
||||
```typescript
|
||||
const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0;
|
||||
const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0;
|
||||
|
||||
if (blAmount && invoiceAmount) {
|
||||
const diff = blAmount - invoiceAmount;
|
||||
// BL > Facture = +XX.XX€ (positif)
|
||||
// BL < Facture = -XX.XX€ (négatif)
|
||||
// BL = Facture = 0.00€ (équilibré)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Couleurs Indicatives**
|
||||
|
||||
```css
|
||||
/* Aucun écart - Parfait */
|
||||
diff === 0 → text-green-600
|
||||
|
||||
/* Écart faible (≤ 10€) - Attention */
|
||||
diffAbs ≤ 10 → text-orange-600
|
||||
|
||||
/* Écart important (> 10€) - Problème */
|
||||
diffAbs > 10 → text-red-600
|
||||
```
|
||||
|
||||
### 4. **Affichage par Cas**
|
||||
|
||||
#### **Cas Complet** (BL + Facture renseignés)
|
||||
- `+15.50€` (BL supérieur de 15.50€)
|
||||
- `-8.25€` (Facture supérieure de 8.25€)
|
||||
- `0.00€` (Montants identiques)
|
||||
|
||||
#### **Cas Incomplet** (données manquantes)
|
||||
- `-` (grisé, données insuffisantes)
|
||||
|
||||
### 5. **Headers de Colonnes**
|
||||
|
||||
```typescript
|
||||
// Manuel
|
||||
<th>Écart</th>
|
||||
|
||||
// Automatique
|
||||
<th>Écart</th>
|
||||
```
|
||||
|
||||
## 🎨 Rendu Visuel
|
||||
|
||||
### Exemples d'Affichage
|
||||
|
||||
| Montant BL | Montant Fact. | Écart | Couleur |
|
||||
|------------|---------------|--------|---------|
|
||||
| 150.00€ | 150.00€ | `0.00€` | 🟢 Vert |
|
||||
| 155.50€ | 150.00€ | `+5.50€` | 🟠 Orange |
|
||||
| 140.00€ | 165.75€ | `-25.75€` | 🔴 Rouge |
|
||||
| 100.00€ | - | `-` | ⚪ Gris |
|
||||
|
||||
### États Visuels par Seuil
|
||||
|
||||
```css
|
||||
/* Équilibré - Vert */
|
||||
.text-green-600 { color: #16a34a; }
|
||||
|
||||
/* Écart modéré - Orange */
|
||||
.text-orange-600 { color: #ea580c; }
|
||||
|
||||
/* Écart important - Rouge */
|
||||
.text-red-600 { color: #dc2626; }
|
||||
|
||||
/* Non applicable - Gris */
|
||||
.text-gray-400 { color: #9ca3af; }
|
||||
```
|
||||
|
||||
## 🔧 Code Implementation
|
||||
|
||||
### Header (Manuel)
|
||||
```typescript
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Écart
|
||||
</th>
|
||||
```
|
||||
|
||||
### Cellule Data
|
||||
```typescript
|
||||
<td className="px-3 py-2 text-sm">
|
||||
{(() => {
|
||||
const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0;
|
||||
const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0;
|
||||
if (blAmount && invoiceAmount) {
|
||||
const diff = blAmount - invoiceAmount;
|
||||
const diffAbs = Math.abs(diff);
|
||||
return (
|
||||
<div className={`font-medium ${
|
||||
diff === 0 ? 'text-green-600' :
|
||||
diffAbs > 10 ? 'text-red-600' : 'text-orange-600'
|
||||
}`}>
|
||||
{diff > 0 ? '+' : ''}{diff.toFixed(2)}€
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <span className="text-gray-400 italic text-xs">-</span>;
|
||||
})()}
|
||||
</td>
|
||||
```
|
||||
|
||||
## 🧪 Tests de Validation
|
||||
|
||||
### Test 1 : Calcul d'Écart
|
||||
1. BL: 100.00€, Facture: 95.00€
|
||||
2. **Vérifier** : Écart = `+5.00€` (orange)
|
||||
|
||||
### Test 2 : Équilibre
|
||||
1. BL: 150.00€, Facture: 150.00€
|
||||
2. **Vérifier** : Écart = `0.00€` (vert)
|
||||
|
||||
### Test 3 : Écart Important
|
||||
1. BL: 200.00€, Facture: 185.00€
|
||||
2. **Vérifier** : Écart = `+15.00€` (rouge)
|
||||
|
||||
### Test 4 : Données Manquantes
|
||||
1. BL: 100.00€, Facture: non renseigné
|
||||
2. **Vérifier** : Écart = `-` (gris)
|
||||
|
||||
## 📊 Status
|
||||
|
||||
- ✅ Colonne Écart ajoutée sur onglet Manuel
|
||||
- ✅ Colonne Écart ajoutée sur onglet Automatique
|
||||
- ✅ Calcul automatique (BL - Facture)
|
||||
- ✅ Couleurs indicatives par seuil
|
||||
- ✅ Gestion des cas de données manquantes
|
||||
- ✅ Format d'affichage cohérent (+/-XX.XX€)
|
||||
|
||||
**La colonne Écart permet maintenant de visualiser instantanément les différences entre BL et factures !**
|
||||
+93
-94
@@ -1,111 +1,110 @@
|
||||
# Fix Complet - Modal de Rapprochement et Actions
|
||||
# Fix Rapprochement - Restrictions Édition
|
||||
|
||||
## ✅ Problèmes Résolus
|
||||
## 🎯 Demandes Utilisateur
|
||||
|
||||
### 1. **Validation impossible** ❌ → ✅
|
||||
**Avant :** Bouton "Valider" sans vérification des données requises
|
||||
**Après :**
|
||||
- Validation conditionnelle : BL number requis
|
||||
- Message d'erreur explicite : "Le numéro de BL est requis. Cliquez sur 'Voir les détails' pour le renseigner"
|
||||
- Action rapide si données complètes, sinon redirection vers modal
|
||||
1. **Les rapprochements validés ne peuvent plus être édités**
|
||||
2. **Ajouter bouton supprimer pour les admins** (onglet automatique)
|
||||
|
||||
### 2. **Pas de modal d'édition** ❌ → ✅
|
||||
**Avant :** Bouton "Voir les détails" ne faisait rien
|
||||
**Après :**
|
||||
- **Modal ReconciliationModal.tsx** complet avec :
|
||||
- Édition BL : N° BL (requis) + Montant BL
|
||||
- Édition Facture : Référence + Montant
|
||||
- Actions : Enregistrer + Valider directement
|
||||
- Distinction Manuel/Automatique avec badges
|
||||
- Validation en temps réel
|
||||
## ✅ Corrections Appliquées
|
||||
|
||||
### 3. **Dévalidation automatique** 🔄 → À tester
|
||||
**Fonctionnalité :**
|
||||
- Bouton dévalider visible pour admin uniquement
|
||||
- Confirmation avant action
|
||||
- API call vers `/api/deliveries/${id}` PUT
|
||||
|
||||
## 🎯 Nouvelles Fonctionnalités
|
||||
|
||||
### Modal de Rapprochement
|
||||
### 1. **Onglet Manuel** - Édition Restreinte
|
||||
```typescript
|
||||
// Ouverture via bouton "Voir les détails"
|
||||
const handleOpenModal = (delivery: any) => {
|
||||
setSelectedDelivery(delivery);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
// Avant : Bouton modifier toujours disponible
|
||||
<button onClick={() => handleOpenModal(delivery)}>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
// Après : Modifier seulement si non validé
|
||||
{!delivery.reconciled && (
|
||||
<button onClick={() => handleOpenModal(delivery)}>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
```
|
||||
|
||||
### Actions Intelligentes
|
||||
- **Validation rapide** : Si BL number présent → validation directe
|
||||
- **Validation guidée** : Si BL number manquant → ouverture modal
|
||||
- **Édition complète** : Modal avec tous les champs BL/Facture
|
||||
### 2. **Onglet Automatique** - Boutons Admin
|
||||
```typescript
|
||||
// Avant : Seulement modifier + dévalider
|
||||
<div className="flex items-center space-x-2">
|
||||
<button onClick={() => handleOpenModal(delivery)}>Modifier</button>
|
||||
{admin && <button>Dévalider</button>}
|
||||
</div>
|
||||
|
||||
### Interface Améliorée
|
||||
- ✅ Boutons d'action fonctionnels avec tooltips
|
||||
- ✅ Indicateurs visuels par statut
|
||||
- ✅ Confirmations de sécurité (window.confirm)
|
||||
- ✅ Messages d'erreur contextuels
|
||||
|
||||
## 📋 Tests à Effectuer en Production
|
||||
|
||||
### Test 1 : Modal d'Édition
|
||||
- [ ] Cliquer sur icône "œil" (Eye) → Modal s'ouvre
|
||||
- [ ] Remplir N° BL obligatoire
|
||||
- [ ] Remplir montants optionnels
|
||||
- [ ] Cliquer "Enregistrer" → Données sauvegardées
|
||||
- [ ] Cliquer "Valider le rapprochement" → Rapprochement validé
|
||||
|
||||
### Test 2 : Validation Rapide
|
||||
- [ ] Livraison avec BL number → Bouton vert "Valider" fonctionne
|
||||
- [ ] Livraison sans BL number → Message "Données manquantes"
|
||||
|
||||
### Test 3 : Dévalidation Automatique
|
||||
- [ ] Se connecter en tant qu'admin
|
||||
- [ ] Onglet "Rapprochement Automatique"
|
||||
- [ ] Cliquer bouton orange "Dévalider" → Confirmation puis action
|
||||
|
||||
### Test 4 : Permissions
|
||||
- **Admin** : Tous boutons visibles (Valider, Supprimer, Dévalider)
|
||||
- **Directeur** : Valider + Supprimer (pas dévalider)
|
||||
- **Manager** : Accès refusé au module
|
||||
- **Employee** : Accès refusé au module
|
||||
|
||||
## 🔧 Debug Dévalidation Automatique
|
||||
|
||||
Si la dévalidation ne fonctionne toujours pas :
|
||||
|
||||
### Vérifier API
|
||||
```bash
|
||||
# Test manuel API
|
||||
curl -X PUT /api/deliveries/[ID] \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"reconciled": false, "validatedAt": null}'
|
||||
// Après : Supprimer + édition restreinte
|
||||
<div className="flex items-center space-x-2">
|
||||
{admin && delivery.reconciled && <button>Dévalider</button>}
|
||||
{admin && <button>Supprimer</button>}
|
||||
{!delivery.reconciled && <button>Modifier</button>}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Vérifier Permissions
|
||||
```javascript
|
||||
// Dans console navigateur
|
||||
console.log("User role:", user?.role);
|
||||
console.log("Is admin:", user?.role === 'admin');
|
||||
## 🔒 Logique des Boutons par État
|
||||
|
||||
### **Rapprochement Non Validé** (`delivery.reconciled = false`)
|
||||
- ✅ **Modifier** : Disponible pour tous les rôles autorisés
|
||||
- ✅ **Valider** : Disponible selon permissions
|
||||
- ✅ **Supprimer** : Admin uniquement
|
||||
|
||||
### **Rapprochement Validé** (`delivery.reconciled = true`)
|
||||
- ❌ **Modifier** : BLOQUÉ (plus d'édition possible)
|
||||
- ✅ **Dévalider** : Admin uniquement
|
||||
- ✅ **Supprimer** : Admin uniquement
|
||||
|
||||
## 🎨 Interface Utilisateur
|
||||
|
||||
### États Visuels
|
||||
```css
|
||||
/* Non validé */
|
||||
bg-white (normal) ou bg-red-50 (problème)
|
||||
|
||||
/* Validé */
|
||||
bg-gray-100 opacity-60 text-gray-500 (grisé)
|
||||
```
|
||||
|
||||
### Vérifier Console Erreurs
|
||||
- F12 → Console → Vérifier erreurs JavaScript
|
||||
- Network → Vérifier requêtes API (200 vs 4xx/5xx)
|
||||
### Boutons par Onglet
|
||||
|
||||
## 🚀 Déploiement
|
||||
#### **Manuel**
|
||||
- Non validé : `[Valider] [Supprimer] [Modifier]`
|
||||
- Validé : `[Dévalider] [Supprimer]` (admin) ou `[]` (autres)
|
||||
|
||||
### Fichiers Modifiés
|
||||
```
|
||||
client/src/components/modals/ReconciliationModal.tsx # ✅ Nouveau
|
||||
client/src/pages/BLReconciliation.tsx # ✅ Modifié
|
||||
#### **Automatique**
|
||||
- Non validé : `[Modifier]`
|
||||
- Validé : `[Dévalider] [Supprimer]` (admin) ou `[]` (autres)
|
||||
|
||||
## 🔧 Imports Requis
|
||||
|
||||
```typescript
|
||||
import { Trash2, Ban, Settings, Check } from 'lucide-react';
|
||||
```
|
||||
|
||||
### Points de Contrôle
|
||||
- ✅ Modal shadcn/ui Dialog (pas React.forwardRef problématique)
|
||||
- ✅ Confirmations via window.confirm (évite erreurs React)
|
||||
- ✅ Gestion d'état locale simple (useState)
|
||||
- ✅ API calls via apiRequest (standardisé)
|
||||
## 🧪 Tests de Validation
|
||||
|
||||
**Status :** ✅ Prêt pour déploiement et test production
|
||||
### Test 1 : Édition Restreinte
|
||||
1. Créer une livraison
|
||||
2. **Vérifier** : Bouton modifier visible
|
||||
3. Valider le rapprochement
|
||||
4. **Vérifier** : Bouton modifier DISPARU
|
||||
5. **Vérifier** : Ligne grisée
|
||||
|
||||
### Test 2 : Boutons Admin (Automatique)
|
||||
1. Se connecter comme admin
|
||||
2. Aller sur onglet "Automatique"
|
||||
3. **Vérifier** : Bouton supprimer visible
|
||||
4. **Vérifier** : Dévalider visible si validé
|
||||
5. **Vérifier** : Modifier visible si non validé
|
||||
|
||||
### Test 3 : Permissions Non-Admin
|
||||
1. Se connecter comme manager/directeur
|
||||
2. **Vérifier** : Pas de bouton supprimer
|
||||
3. **Vérifier** : Pas de bouton dévalider
|
||||
4. **Vérifier** : Modifier visible si non validé
|
||||
|
||||
## 📊 Status
|
||||
|
||||
- ✅ Édition bloquée pour rapprochements validés
|
||||
- ✅ Bouton supprimer ajouté pour admins (automatique)
|
||||
- ✅ Logique cohérente entre onglets manuel/automatique
|
||||
- ✅ Permissions respectées selon le rôle
|
||||
- ✅ Interface claire avec grisage des validés
|
||||
|
||||
**Les rapprochements validés sont maintenant protégés contre l'édition !**
|
||||
@@ -355,6 +355,9 @@ export default function BLReconciliation() {
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Montant Fact.
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Écart
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Magasin
|
||||
</th>
|
||||
@@ -415,6 +418,25 @@ export default function BLReconciliation() {
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
{(() => {
|
||||
const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0;
|
||||
const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0;
|
||||
if (blAmount && invoiceAmount) {
|
||||
const diff = blAmount - invoiceAmount;
|
||||
const diffAbs = Math.abs(diff);
|
||||
return (
|
||||
<div className={`font-medium ${
|
||||
diff === 0 ? 'text-green-600' :
|
||||
diffAbs > 10 ? 'text-red-600' : 'text-orange-600'
|
||||
}`}>
|
||||
{diff > 0 ? '+' : ''}{diff.toFixed(2)}€
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <span className="text-gray-400 italic text-xs">-</span>;
|
||||
})()}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
<div className="text-gray-900">
|
||||
{delivery.group?.name}
|
||||
@@ -468,14 +490,16 @@ export default function BLReconciliation() {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Bouton Modifier toujours disponible */}
|
||||
<button
|
||||
onClick={() => handleOpenModal(delivery)}
|
||||
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
||||
title="Modifier les données de rapprochement"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
{/* Bouton Modifier : disponible seulement si pas encore validé */}
|
||||
{!delivery.reconciled && (
|
||||
<button
|
||||
onClick={() => handleOpenModal(delivery)}
|
||||
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
||||
title="Modifier les données de rapprochement"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -544,6 +568,9 @@ export default function BLReconciliation() {
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Montant Fact.
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Écart
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Magasin
|
||||
</th>
|
||||
@@ -611,6 +638,25 @@ export default function BLReconciliation() {
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
{(() => {
|
||||
const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0;
|
||||
const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0;
|
||||
if (blAmount && invoiceAmount) {
|
||||
const diff = blAmount - invoiceAmount;
|
||||
const diffAbs = Math.abs(diff);
|
||||
return (
|
||||
<div className={`font-medium ${
|
||||
diff === 0 ? 'text-green-600' :
|
||||
diffAbs > 10 ? 'text-red-600' : 'text-orange-600'
|
||||
}`}>
|
||||
{diff > 0 ? '+' : ''}{diff.toFixed(2)}€
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <span className="text-gray-400 italic text-xs">-</span>;
|
||||
})()}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
<div className="text-gray-900">
|
||||
{delivery.group?.name}
|
||||
@@ -618,15 +664,7 @@ export default function BLReconciliation() {
|
||||
</td>
|
||||
<td className="px-3 py-2 text-sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => handleOpenModal(delivery)}
|
||||
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
||||
title="Modifier les données de rapprochement"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
{/* Dévalider automatique - admin uniquement */}
|
||||
{user?.role === 'admin' && (
|
||||
{user?.role === 'admin' && delivery.reconciled && (
|
||||
<button
|
||||
onClick={() => handleDevalidateAutoReconciliation(delivery.id)}
|
||||
className="text-gray-600 hover:text-orange-600 transition-colors duration-200 p-1 hover:bg-orange-50 rounded"
|
||||
@@ -635,6 +673,25 @@ export default function BLReconciliation() {
|
||||
<Ban className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{user?.role === 'admin' && (
|
||||
<button
|
||||
onClick={() => handleDeleteDelivery(delivery.id)}
|
||||
className="text-gray-600 hover:text-red-600 transition-colors duration-200 p-1 hover:bg-red-50 rounded"
|
||||
title="Supprimer la livraison"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
{/* Bouton Modifier : disponible seulement si pas encore validé */}
|
||||
{!delivery.reconciled && (
|
||||
<button
|
||||
onClick={() => handleOpenModal(delivery)}
|
||||
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
||||
title="Modifier les données de rapprochement"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in new issue
Block a user