diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 2249df9..075e16d 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -81,13 +81,13 @@ export default function Sidebar() { path: "/orders", label: "Commandes", icon: Package, - roles: ["admin", "directeur", "manager", "employee"] + roles: ["admin", "directeur", "manager"] }, { path: "/deliveries", label: "Livraisons", icon: Truck, - roles: ["admin", "directeur", "manager", "employee"] + roles: ["admin", "directeur", "manager"] }, { path: "/bl-reconciliation", diff --git a/client/src/components/modals/QuickCreateMenu.tsx b/client/src/components/modals/QuickCreateMenu.tsx index d617ea2..730983a 100644 --- a/client/src/components/modals/QuickCreateMenu.tsx +++ b/client/src/components/modals/QuickCreateMenu.tsx @@ -1,6 +1,8 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Package, Truck } from "lucide-react"; +import { useAuthUnified } from "@/hooks/useAuthUnified"; +import { hasPermission } from "@/lib/permissions"; interface QuickCreateMenuProps { isOpen: boolean; @@ -15,6 +17,11 @@ export default function QuickCreateMenu({ onCreateOrder, onCreateDelivery, }: QuickCreateMenuProps) { + const { user } = useAuthUnified(); + + const canCreateOrders = hasPermission(user?.role || '', 'orders', 'create'); + const canCreateDeliveries = hasPermission(user?.role || '', 'deliveries', 'create'); + return ( @@ -25,33 +32,43 @@ export default function QuickCreateMenu({

- + {canCreateOrders && ( + + )} - + )} + + {!canCreateOrders && !canCreateDeliveries && ( +
+

Vous n'avez pas les permissions nécessaires pour créer des commandes ou livraisons.

-
-

Nouvelle Livraison

-

Planifier une livraison

-
- + )}
diff --git a/client/src/pages/Calendar.tsx b/client/src/pages/Calendar.tsx index b94b329..6413a82 100644 --- a/client/src/pages/Calendar.tsx +++ b/client/src/pages/Calendar.tsx @@ -14,6 +14,7 @@ import { apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import { format, startOfMonth, endOfMonth } from "date-fns"; import { fr } from "date-fns/locale"; +import { hasPermission } from "@/lib/permissions"; export default function Calendar() { const { user } = useAuthUnified(); @@ -250,14 +251,15 @@ export default function Calendar() {
- - + {(hasPermission(user?.role || '', 'orders', 'create') || hasPermission(user?.role || '', 'deliveries', 'create')) && ( + + )}
diff --git a/replit.md b/replit.md index 3ccbd29..166ac0e 100644 --- a/replit.md +++ b/replit.md @@ -162,4 +162,51 @@ Preferred communication style: Simple, everyday language. - Added automatic order status synchronization in `updateDelivery` method when delivery marked as delivered - Created `fix-delivery-order-sync.sql` script to correct existing production data inconsistencies - Improved Calendar date initialization to show current month instead of hardcoded July 2025 -- **Result**: Calendar now properly displays delivery dates and automatically synchronizes order statuses when deliveries are completed \ No newline at end of file +- **Result**: Calendar now properly displays delivery dates and automatically synchronizes order statuses when deliveries are completed + +### August 12, 2025 - Employee Permissions Restriction & Database Schema Download + +#### Employee Role Permissions Update +- **Issue**: Employee role had excessive access to orders and deliveries modules, but needed restricted calendar access +- **Root Cause**: Permission system allowed employees full access to create orders/deliveries from calendar and sidebar navigation +- **Solution Implemented**: + - Updated `shared/permissions.ts`: Removed all permissions for employees on 'orders' and 'deliveries' modules + - Modified `client/src/components/Sidebar.tsx`: Removed orders and deliveries menu items for employee role + - Enhanced `client/src/pages/Calendar.tsx`: Added permission checks to hide "Nouveau" button for employees + - Updated `client/src/components/modals/QuickCreateMenu.tsx`: Added role-based filtering for order/delivery creation options +- **Employee Access Rules**: + - **Calendar**: View-only access, cannot create orders or deliveries + - **Sidebar**: No access to Orders and Deliveries menu items + - **Customer Orders & DLC**: Maintains supplier list access for creating customer orders and DLC entries +- **Result**: Employees now have appropriate restricted access while maintaining necessary supplier information for their workflows + +#### Database Schema Download Feature +- **Issue**: Database schema scan results were only available in server logs, needed downloadable reports +- **Solution Implemented**: + - Created new API route `/api/debug/download-schema` for production schema downloads + - Added comprehensive schema report generation with tables, columns, constraints, and record counts + - Enhanced `client/src/pages/DatabaseDebug.tsx`: Added download button that appears after successful scans + - Implemented proper file download headers with automatic filename generation + - Added admin-only access restriction and production environment validation +- **Download Report Features**: + - Complete database structure with column details and constraints + - Record counts per table for data overview + - Foreign key relationships mapping + - Formatted text file with timestamps and user information +- **Result**: Administrators can now download comprehensive database schema reports for documentation and analysis + +#### Employee Supplier Access Fix +- **Issue**: Employee role could not access supplier lists in Customer Orders and DLC modules on production server +- **Root Cause**: API route `/api/suppliers` was restricted to admin and manager roles only, excluding employees and directeurs +- **Solution Implemented**: + - Updated `/api/suppliers` GET route: Added employee and directeur role access for read operations + - Updated `/api/suppliers` POST route: Added directeur role access for creation operations + - Updated `/api/suppliers` PUT route: Added directeur role access for edit operations + - Updated `/api/suppliers` DELETE route: Restricted to admin and directeur only (removed manager access) + - Created test script `test-employee-supplier-access.js` for production verification +- **Access Matrix Updated**: + - **GET /api/suppliers**: admin, directeur, manager, employee (all roles can read) + - **POST /api/suppliers**: admin, directeur, manager (creation permissions) + - **PUT /api/suppliers**: admin, directeur, manager (edit permissions) + - **DELETE /api/suppliers**: admin, directeur only (delete permissions) +- **Result**: Employees can now access supplier lists in Customer Orders and DLC modules while maintaining appropriate write restrictions \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index d67611a..0f85bb9 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -188,7 +188,7 @@ export async function registerRoutes(app: Express): Promise { app.get('/api/suppliers', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || (user.role !== 'admin' && user.role !== 'manager')) { + if (!user || (user.role !== 'admin' && user.role !== 'manager' && user.role !== 'directeur' && user.role !== 'employee')) { return res.status(403).json({ message: "Insufficient permissions" }); } @@ -237,8 +237,8 @@ export async function registerRoutes(app: Express): Promise { console.log('✅ User found:', { username: user.username, role: user.role }); // Vérifier les permissions - if (user.role !== 'admin' && user.role !== 'manager') { - console.error('❌ Insufficient permissions:', { userRole: user.role, required: ['admin', 'manager'] }); + if (user.role !== 'admin' && user.role !== 'manager' && user.role !== 'directeur') { + console.error('❌ Insufficient permissions:', { userRole: user.role, required: ['admin', 'manager', 'directeur'] }); return res.status(403).json({ message: "Insufficient permissions" }); } @@ -279,7 +279,7 @@ export async function registerRoutes(app: Express): Promise { app.put('/api/suppliers/:id', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || (user.role !== 'admin' && user.role !== 'manager')) { + if (!user || (user.role !== 'admin' && user.role !== 'manager' && user.role !== 'directeur')) { return res.status(403).json({ message: "Insufficient permissions" }); } @@ -296,7 +296,7 @@ export async function registerRoutes(app: Express): Promise { app.delete('/api/suppliers/:id', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || (user.role !== 'admin' && user.role !== 'manager')) { + if (!user || (user.role !== 'admin' && user.role !== 'directeur')) { return res.status(403).json({ message: "Insufficient permissions" }); } diff --git a/shared/permissions.ts b/shared/permissions.ts index 00af99a..0cd843a 100644 --- a/shared/permissions.ts +++ b/shared/permissions.ts @@ -32,20 +32,20 @@ const PERMISSIONS: Record> = { employee: ['view'] }, - // Commandes - Admin/Directeur tout, Manager tout sauf delete, Employé view + // Commandes - Admin/Directeur tout, Manager tout sauf delete, Employé aucun accès orders: { admin: ['view', 'create', 'edit', 'delete'], directeur: ['view', 'create', 'edit', 'delete'], manager: ['view', 'create', 'edit'], - employee: ['view'] + employee: [] }, - // Livraisons - Admin/Directeur tout, Manager tout sauf delete, Employé view + // Livraisons - Admin/Directeur tout, Manager tout sauf delete, Employé aucun accès deliveries: { admin: ['view', 'create', 'edit', 'delete'], directeur: ['view', 'create', 'edit', 'delete'], manager: ['view', 'create', 'edit'], - employee: ['view'] + employee: [] }, // Rapprochement - Admin tout, Directeur tout sauf delete, Manager/Employé rien diff --git a/test-employee-supplier-access.js b/test-employee-supplier-access.js new file mode 100644 index 0000000..569f5db --- /dev/null +++ b/test-employee-supplier-access.js @@ -0,0 +1,152 @@ +#!/usr/bin/env node + +/** + * Script de test pour vérifier l'accès des employés aux fournisseurs + * À exécuter sur le serveur de production pour diagnostiquer les problèmes d'accès + */ + +const https = require('https'); +const fs = require('fs'); + +// Configuration pour votre serveur de production +const PRODUCTION_URL = process.env.PRODUCTION_URL || 'https://votre-serveur-production.com'; +const TEST_EMPLOYEE_CREDENTIALS = { + username: process.env.TEST_EMPLOYEE_USERNAME || 'test_employee', + password: process.env.TEST_EMPLOYEE_PASSWORD || 'password123' +}; + +console.log('🧪 === TEST D\'ACCÈS FOURNISSEURS POUR EMPLOYÉS ===\n'); + +async function makeRequest(path, method = 'GET', data = null, cookies = '') { + return new Promise((resolve, reject) => { + const url = new URL(path, PRODUCTION_URL); + const options = { + hostname: url.hostname, + port: url.port || (url.protocol === 'https:' ? 443 : 80), + path: url.pathname + url.search, + method: method, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'LogiFlow-Test-Script/1.0', + ...(cookies ? { 'Cookie': cookies } : {}), + ...(data ? { 'Content-Length': Buffer.byteLength(JSON.stringify(data)) } : {}) + } + }; + + const req = https.request(options, (res) => { + let body = ''; + res.on('data', (chunk) => body += chunk); + res.on('end', () => { + try { + const parsed = JSON.parse(body); + resolve({ + status: res.statusCode, + headers: res.headers, + data: parsed + }); + } catch (e) { + resolve({ + status: res.statusCode, + headers: res.headers, + data: body + }); + } + }); + }); + + req.on('error', reject); + + if (data) { + req.write(JSON.stringify(data)); + } + + req.end(); + }); +} + +async function testEmployeeSupplierAccess() { + try { + console.log('1️⃣ Test de connexion employé...'); + + // Étape 1: Se connecter avec un compte employé + const loginResponse = await makeRequest('/api/login', 'POST', TEST_EMPLOYEE_CREDENTIALS); + + console.log(` Status: ${loginResponse.status}`); + console.log(` Response: ${JSON.stringify(loginResponse.data, null, 2)}\n`); + + if (loginResponse.status !== 200) { + console.error('❌ Échec de connexion employé'); + console.error(' Vérifiez que le compte employé existe et que les identifiants sont corrects'); + return; + } + + // Extraire les cookies de session + const setCookieHeaders = loginResponse.headers['set-cookie'] || []; + const cookies = setCookieHeaders.join('; '); + + console.log('2️⃣ Test d\'accès aux fournisseurs...'); + + // Étape 2: Tester l'accès à la liste des fournisseurs + const suppliersResponse = await makeRequest('/api/suppliers', 'GET', null, cookies); + + console.log(` Status: ${suppliersResponse.status}`); + console.log(` Response: ${JSON.stringify(suppliersResponse.data, null, 2)}\n`); + + if (suppliersResponse.status === 200) { + console.log('✅ SUCCÈS: L\'employé peut accéder aux fournisseurs'); + console.log(` Nombre de fournisseurs retournés: ${Array.isArray(suppliersResponse.data) ? suppliersResponse.data.length : 'N/A'}`); + } else if (suppliersResponse.status === 403) { + console.log('❌ ÉCHEC: L\'employé n\'a pas l\'autorisation d\'accéder aux fournisseurs'); + console.log(' Le problème est confirmé - permissions insuffisantes'); + } else { + console.log(`⚠️ ERREUR INATTENDUE: Status ${suppliersResponse.status}`); + } + + console.log('\n3️⃣ Test d\'accès aux fournisseurs DLC...'); + + // Étape 3: Tester l'accès aux fournisseurs DLC spécifiquement + const dlcSuppliersResponse = await makeRequest('/api/suppliers?dlc=true', 'GET', null, cookies); + + console.log(` Status: ${dlcSuppliersResponse.status}`); + console.log(` Response: ${JSON.stringify(dlcSuppliersResponse.data, null, 2)}\n`); + + if (dlcSuppliersResponse.status === 200) { + console.log('✅ SUCCÈS: L\'employé peut accéder aux fournisseurs DLC'); + console.log(` Nombre de fournisseurs DLC retournés: ${Array.isArray(dlcSuppliersResponse.data) ? dlcSuppliersResponse.data.length : 'N/A'}`); + } else if (dlcSuppliersResponse.status === 403) { + console.log('❌ ÉCHEC: L\'employé n\'a pas l\'autorisation d\'accéder aux fournisseurs DLC'); + } + + // Résumé final + console.log('\n📊 === RÉSUMÉ DU TEST ==='); + console.log(`Connexion employé: ${loginResponse.status === 200 ? '✅ OK' : '❌ ÉCHEC'}`); + console.log(`Accès fournisseurs: ${suppliersResponse.status === 200 ? '✅ OK' : '❌ ÉCHEC'}`); + console.log(`Accès fournisseurs DLC: ${dlcSuppliersResponse.status === 200 ? '✅ OK' : '❌ ÉCHEC'}`); + + if (suppliersResponse.status === 200 && dlcSuppliersResponse.status === 200) { + console.log('\n🎉 TOUS LES TESTS PASSÉS - L\'accès employé aux fournisseurs fonctionne correctement'); + } else { + console.log('\n⚠️ PROBLÈME DÉTECTÉ - Vérifiez les permissions serveur et la configuration des rôles'); + } + + } catch (error) { + console.error('💥 Erreur lors du test:', error.message); + console.error(' Vérifiez que le serveur de production est accessible'); + } +} + +// Instructions d'utilisation +console.log('📋 Instructions:'); +console.log('1. Assurez-vous d\'avoir un compte employé de test'); +console.log('2. Configurez les variables d\'environnement si nécessaire:'); +console.log(' - PRODUCTION_URL=https://votre-serveur.com'); +console.log(' - TEST_EMPLOYEE_USERNAME=nom_utilisateur'); +console.log(' - TEST_EMPLOYEE_PASSWORD=mot_de_passe'); +console.log('3. Exécutez: node test-employee-supplier-access.js\n'); + +// Exécuter le test si le script est appelé directement +if (require.main === module) { + testEmployeeSupplierAccess().catch(console.error); +} + +module.exports = { testEmployeeSupplierAccess }; \ No newline at end of file