diff --git a/attached_assets/Pasted--Database-initialization-NODE-ENV-production-isProduction-true-h-1755083906219_1755083906219.txt b/attached_assets/Pasted--Database-initialization-NODE-ENV-production-isProduction-true-h-1755083906219_1755083906219.txt new file mode 100644 index 0000000..d73adee --- /dev/null +++ b/attached_assets/Pasted--Database-initialization-NODE-ENV-production-isProduction-true-h-1755083906219_1755083906219.txt @@ -0,0 +1,140 @@ + + +🔗 Database initialization: { + + NODE_ENV: 'production', + + isProduction: true, + + hasDbUrl: true, + + dbHost: 'logiflow-db' + +} + +🐳 PRODUCTION: Using standard PostgreSQL + +✅ PostgreSQL connection test successful + +🐳 PRODUCTION: Using PostgreSQL storage + +🐳 PRODUCTION: Local auth configured with PostgreSQL sessions + +🐳 PRODUCTION: Starting LogiFlow application + +🐳 Environment: { NODE_ENV: 'production', DATABASE_URL: 'Present', PORT: '3000' } + +🔍 Using development storage and authentication + +🔧 Using memory session store for development + +Warning: connect.session() MemoryStore is not + +designed for a production environment, as it will leak + +memory, and will not scale past a single process. + +🐳 Serving static files from: /app/dist/public + +🐳 PRODUCTION: LogiFlow serving on port 3000 + +✅ Production admin user found: { id: 'admin_local', username: 'admin', passwordFormat: 'present' } + +✅ Admin user exists, preserving current password + +HEAD /api/health 200 in 5ms + +📄 SPA: Serving index.html for /bl-reconciliation + +🔍 PRODUCTION /api/user - req.user: { + + id: 'admin_local', + + role: 'admin', + + hasUserGroups: true, + + userGroupsLength: 0, + + userGroups: [] + +} + +GET /api/user 304 in 10ms + +🔍 PRODUCTION /api/user - req.user: { + + id: 'admin_local', + + role: 'admin', + + hasUserGroups: true, + + userGroupsLength: 0, + + userGroups: [] + +} + +GET /api/user 304 in 9ms + +GET /api/suppliers 304 in 19ms + +🔍 PRODUCTION /api/user - req.user: { + + id: 'admin_local', + + role: 'admin', + + hasUserGroups: true, + + userGroupsLength: 0, + + userGroups: [] + +} + +GET /api/user 304 in 20ms + +🔍 PRODUCTION /api/user - req.user: { + + id: 'admin_local', + + role: 'admin', + + hasUserGroups: true, + + userGroupsLength: 0, + + userGroups: [] + +} + +GET /api/user 304 in 8ms + +Deliveries API called with: { + + startDate: undefined, + + endDate: undefined, + + storeId: undefined, + + withBL: undefined, + + userRole: 'admin' + +} + +Admin filtering deliveries with groupIds: undefined + +Fetching all deliveries + +Deliveries returned: 81 items + +GET /api/deliveries 304 in 50ms + +GET /api/groups 304 in 36ms + +HEAD /api/health 200 in 1ms + diff --git a/attached_assets/{4EAE4304-85E8-49AB-B5B3-7EC51B7D87D2}_1755084003944.png b/attached_assets/{4EAE4304-85E8-49AB-B5B3-7EC51B7D87D2}_1755084003944.png new file mode 100644 index 0000000..94bc11b Binary files /dev/null and b/attached_assets/{4EAE4304-85E8-49AB-B5B3-7EC51B7D87D2}_1755084003944.png differ diff --git a/attached_assets/{9D59854C-8390-4082-84F2-68DD6746FA20}_1755083891815.png b/attached_assets/{9D59854C-8390-4082-84F2-68DD6746FA20}_1755083891815.png new file mode 100644 index 0000000..a36100f Binary files /dev/null and b/attached_assets/{9D59854C-8390-4082-84F2-68DD6746FA20}_1755083891815.png differ diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index 95ccfee..74c5c29 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -142,9 +142,9 @@ export default function BLReconciliation() { supplier: delivery.supplier }); - if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId) { + if (!delivery.group?.nocodbTableName && !delivery.group?.nocodbConfigId && !delivery.group?.webhookUrl) { toast({ - title: "Vérification non disponible", + title: "Vérification non disponible", description: "Ce magasin n'a pas de configuration NocoDB", variant: "destructive", }); @@ -178,6 +178,11 @@ export default function BLReconciliation() { } const deliveries = await response.json(); + + // Debug : examiner les données des groupes en production + console.log('🔍 DEBUG Production - Première livraison:', deliveries[0]); + console.log('🔍 DEBUG Production - Groupe de la première livraison:', deliveries[0]?.group); + console.log('🔍 DEBUG Production - Champs disponibles dans group:', deliveries[0]?.group ? Object.keys(deliveries[0].group) : 'Pas de groupe'); const filtered = Array.isArray(deliveries) ? deliveries.filter((d: any) => d.status === 'delivered') : []; return filtered.sort((a: any, b: any) => new Date(b.deliveredDate || b.updatedAt).getTime() - new Date(a.deliveredDate || a.updatedAt).getTime()); @@ -641,7 +646,7 @@ export default function BLReconciliation() { {/* Icône de vérification de facture */} - {(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId) && ( + {(delivery.group?.nocodbTableName || delivery.group?.nocodbConfigId || delivery.group?.webhookUrl) && (
{verifyingDeliveries.has(delivery.id) ? ( diff --git a/server/routes.ts b/server/routes.ts index 47c3750..98f134c 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -33,7 +33,8 @@ import { insertCustomerOrderFrontendSchema, insertDlcProductSchema, insertDlcProductFrontendSchema, - insertTaskSchema + insertTaskSchema, + insertNocodbConfigSchema } from "@shared/schema"; import { hasPermission } from "@shared/permissions"; import { z } from "zod"; @@ -1772,32 +1773,10 @@ RÉSUMÉ DU SCAN } const configs = await storage.getNocodbConfigs(); - // Assurer que la réponse est toujours un array - res.json(Array.isArray(configs) ? configs : []); + res.json(configs); } catch (error) { console.error('Error fetching NocoDB configs:', error); - res.status(500).json({ message: 'Erreur lors de la récupération des configurations' }); - } - }); - - app.get('/api/nocodb-config/:id', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' }); - } - - const id = parseInt(req.params.id); - const config = await storage.getNocodbConfig(id); - - if (!config) { - return res.status(404).json({ message: 'Configuration non trouvée' }); - } - - res.json(config); - } catch (error) { - console.error('Error fetching NocoDB config:', error); - res.status(500).json({ message: 'Erreur lors de la récupération de la configuration' }); + res.status(500).json({ error: 'Failed to fetch configurations' }); } }); @@ -1808,16 +1787,15 @@ RÉSUMÉ DU SCAN return res.status(403).json({ message: 'Accès refusé. Seuls les administrateurs peuvent gérer les configurations NocoDB.' }); } - const configData = { + const configData = insertNocodbConfigSchema.parse({ ...req.body, - createdBy: req.user.claims ? req.user.claims.sub : req.user.id, - }; - + createdBy: user.id + }); const config = await storage.createNocodbConfig(configData); res.status(201).json(config); } catch (error) { console.error('Error creating NocoDB config:', error); - res.status(500).json({ message: 'Erreur lors de la création de la configuration' }); + res.status(500).json({ error: 'Failed to create configuration' }); } }); @@ -1829,11 +1807,12 @@ RÉSUMÉ DU SCAN } const id = parseInt(req.params.id); - const config = await storage.updateNocodbConfig(id, req.body); + const configData = insertNocodbConfigSchema.partial().parse(req.body); + const config = await storage.updateNocodbConfig(id, configData); res.json(config); } catch (error) { console.error('Error updating NocoDB config:', error); - res.status(500).json({ message: 'Erreur lors de la mise à jour de la configuration' }); + res.status(500).json({ error: 'Failed to update configuration' }); } }); @@ -1849,994 +1828,28 @@ RÉSUMÉ DU SCAN res.status(204).send(); } catch (error) { console.error('Error deleting NocoDB config:', error); - res.status(500).json({ message: 'Erreur lors de la suppression de la configuration' }); + res.status(500).json({ error: 'Failed to delete configuration' }); } }); - - - - // Get all roles - - // Get all permissions - - // Get permissions for a specific role - - // Set permissions for a role - - // Set roles for a user - app.post('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => { + app.get('/api/nocodb-config/active', isAuthenticated, async (req: any, res) => { try { const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Access denied" }); - } - - const { userId } = req.params; - const { roleIds } = req.body; - - if (!Array.isArray(roleIds)) { - return res.status(400).json({ message: "roleIds must be an array" }); - } - - const assignedBy = req.user.claims ? req.user.claims.sub : req.user.id; - await storage.setUserRoles(userId, roleIds, assignedBy); - console.log(`✅ Roles updated for user ${userId}:`, roleIds); - res.json({ message: "User roles updated successfully" }); - } catch (error) { - console.error("Error setting user roles:", error); - res.status(500).json({ message: "Failed to update user roles" }); - } - }); - - // Get roles for a specific user - app.get('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => { - try { - const user = await storage.getUserWithGroups(req.user.claims ? req.user.claims.sub : req.user.id); - if (!user || user.role !== 'admin') { - return res.status(403).json({ message: "Access denied" }); - } - - const { userId } = req.params; - const userRoles = await storage.getUserRoles(userId); - res.json(Array.isArray(userRoles) ? userRoles : []); - } catch (error) { - console.error("Error fetching user roles:", error); - res.status(500).json([]); - } - }); - - // Customer Orders routes - app.get('/api/customer-orders', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); if (!user) { - return res.status(404).json({ message: "User not found" }); + return res.status(403).json({ message: 'Accès refusé.' }); } - const { storeId } = req.query; - - // Determine which groups to show - let groupIds; - if (user.role === 'admin' && storeId) { - // Admin filtering by specific store - groupIds = [parseInt(storeId.toString())]; - console.log("Customer orders - Admin filtering by store:", { storeId, groupIds }); - } else if (user.role === 'admin') { - // Admin viewing all stores - get all groups - const allGroups = await storage.getGroups(); - groupIds = allGroups.map(g => g.id); - console.log("Customer orders - Admin viewing all stores:", { groupCount: groupIds.length }); - } else { - // Non-admin users see only their assigned stores - groupIds = user.userGroups.map(ug => ug.groupId); - console.log("Customer orders - User assigned stores:", { groupIds }); - } - - const customerOrders = await storage.getCustomerOrders(groupIds); - console.log("Customer orders returned from storage:", customerOrders?.length || 0, "items"); - res.json(customerOrders || []); + const activeConfig = await storage.getActiveNocodbConfig(); + res.json(activeConfig || null); } catch (error) { - console.error("Error fetching customer orders:", error); - res.status(500).json({ message: "Failed to fetch customer orders" }); + console.error('Error fetching active NocoDB config:', error); + res.status(500).json({ error: 'Failed to fetch active configuration' }); } }); - app.get('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const customerOrder = await storage.getCustomerOrder(id); - - if (!customerOrder) { - return res.status(404).json({ message: "Customer order not found" }); - } - - // Check if user has access to this order's group - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - if (user.role !== 'admin') { - const userGroupIds = user.userGroups.map(ug => ug.groupId); - if (!userGroupIds.includes(customerOrder.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - res.json(customerOrder); - } catch (error) { - console.error("Error fetching customer order:", error); - res.status(500).json({ message: "Failed to fetch customer order" }); - } - }); - - app.post('/api/customer-orders', isAuthenticated, async (req: any, res) => { - try { - console.log("🔍 CUSTOMER ORDER BACKEND - Raw body received:", req.body); - console.log("🔍 CUSTOMER ORDER BACKEND - Body type:", typeof req.body); - console.log("🔍 CUSTOMER ORDER BACKEND - Body keys:", req.body ? Object.keys(req.body) : 'no keys'); - console.log("🔍 CUSTOMER ORDER BACKEND - Original groupId from frontend:", req.body.groupId, typeof req.body.groupId); - - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // Fix groupId BEFORE validation - use user's assigned group or fallback - let finalGroupId = req.body.groupId; - - console.log("🔍 Customer Order GroupId Debug Production:", { - originalGroupId: req.body.groupId, - userGroups: user.userGroups?.map(ug => ({ - groupId: ug.groupId, - groupName: ug.group?.name, - rawGroup: ug.group - })), - userGroupsLength: user.userGroups?.length, - firstUserGroup: user.userGroups?.[0] - }); - - if (!finalGroupId || finalGroupId === undefined || finalGroupId === null || finalGroupId === '') { - if (user.userGroups?.[0]?.groupId) { - finalGroupId = user.userGroups[0].groupId; - console.log("🔧 Customer Order Backend Fix: Using user's assigned group:", finalGroupId); - } else if (user.userGroups?.[0]?.group?.id) { - // Alternative access pattern for production - finalGroupId = user.userGroups[0].group.id; - console.log("🔧 Customer Order Backend Fix: Using user's group.id:", finalGroupId); - } else { - finalGroupId = 1; // Emergency fallback - console.log("🚨 Customer Order Backend Fix: Using emergency fallback groupId:", finalGroupId); - } - } - - console.log("🔍 GroupId resolution debug:", { - originalGroupId: req.body.groupId, - finalGroupId, - typeOfFinal: typeof finalGroupId, - userGroups: user.userGroups?.map(ug => ({groupId: ug.groupId, groupName: ug.group?.name})) - }); - - // Prepare body with valid groupId for validation - const bodyWithGroupId = { - ...req.body, - groupId: finalGroupId - }; - - // Use frontend schema and map to backend fields - console.log("🔍 Pre-parse bodyWithGroupId:", bodyWithGroupId); - console.log("🔍 GroupId value and type:", { groupId: bodyWithGroupId.groupId, type: typeof bodyWithGroupId.groupId }); - - const frontendData = insertCustomerOrderFrontendSchema.parse(bodyWithGroupId); - console.log("Frontend data parsed with fixed groupId:", frontendData); - - // Map frontend fields to backend schema - const backendData = { - orderTaker: frontendData.orderTaker || `${user.firstName} ${user.lastName}`.trim() || user.username, - customerName: frontendData.customerName, - customerPhone: frontendData.contactNumber, - customerEmail: frontendData.customerEmail, - productDesignation: frontendData.productName, - productReference: frontendData.productReference, - gencode: frontendData.gencode || "", - quantity: frontendData.quantity, - supplierId: frontendData.supplierId || 1, // Default supplier - groupId: finalGroupId, // Use fixed groupId - deposit: frontendData.deposit, - isPromotionalPrice: frontendData.isPromotionalPrice, - notes: frontendData.notes, - createdBy: userId, - }; - - console.log("Backend data mapped:", backendData); - - // REMOVED: All role restrictions - tous les rôles peuvent créer des commandes client - console.log("🔍 PRODUCTION DEBUG - Creating customer order with data:", { - userId, - userRole: user.role, - userGroups: user.userGroups?.map(ug => ({groupId: ug.groupId, groupName: ug.group?.name})), - originalGroupId: req.body.groupId, - finalGroupId: backendData.groupId, - backendDataGroupId: backendData.groupId - }); - - console.log("🔍 PRODUCTION DEBUG - Calling storage.createCustomerOrder with groupId:", backendData.groupId); - const customerOrder = await storage.createCustomerOrder(backendData); - console.log("🔍 PRODUCTION DEBUG - Customer order created with groupId:", customerOrder.groupId, "- Should be:", finalGroupId); - res.status(201).json(customerOrder); - } catch (error) { - console.error("Error creating customer order:", error); - if (error instanceof z.ZodError) { - return res.status(400).json({ message: "Invalid data", errors: error.errors }); - } - res.status(500).json({ message: "Failed to create customer order" }); - } - }); - - app.put('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // Check if order exists and user has access - const existingOrder = await storage.getCustomerOrder(id); - if (!existingOrder) { - return res.status(404).json({ message: "Customer order not found" }); - } - - // Check edit permissions - if (!hasPermission(user.role, 'customer-orders', 'edit')) { - return res.status(403).json({ message: "Insufficient permissions to edit customer orders" }); - } - - if (user.role !== 'admin') { - const userGroupIds = user.userGroups?.map((ug: any) => ug.groupId) || []; - if (!userGroupIds.includes(existingOrder.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - const data = insertCustomerOrderSchema.partial().parse(req.body); - const customerOrder = await storage.updateCustomerOrder(id, data); - res.json(customerOrder); - } catch (error) { - console.error("Error updating customer order:", error); - res.status(500).json({ message: "Failed to update customer order" }); - } - }); - - app.delete('/api/customer-orders/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // Check if order exists and user has access - const existingOrder = await storage.getCustomerOrder(id); - if (!existingOrder) { - return res.status(404).json({ message: "Customer order not found" }); - } - - // Check permissions using the shared permission system - if (!hasPermission(user.role, 'customer-orders', 'delete')) { - return res.status(403).json({ message: "Insufficient permissions to delete customer orders" }); - } - - await storage.deleteCustomerOrder(id); - res.status(204).send(); - } catch (error) { - console.error("Error deleting customer order:", error); - res.status(500).json({ message: "Failed to delete customer order" }); - } - }); - - // ===== ROLE MANAGEMENT ROUTES ===== - - // Roles routes - - - - - - - - - - - - // Role-Permission association routes - - - // User-Role association routes - app.get('/api/users/:userId/roles', 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') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const userId = req.params.userId; - const userRoles = await storage.getUserRoles(userId); - res.json(userRoles); - } catch (error) { - console.error("Error fetching user roles:", error); - res.status(500).json({ message: "Failed to fetch user roles" }); - } - }); - - // POST route for user roles (used by frontend) - app.post('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => { - try { - const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!currentUser || currentUser.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const userId = req.params.userId; - const { roleIds } = req.body; - - console.log("🔧 POST User roles API called:", { userId, roleIds, assignedBy: currentUser.id }); - - if (!Array.isArray(roleIds)) { - return res.status(400).json({ message: "roleIds must be an array" }); - } - - const assignedBy = currentUser.id; - await storage.setUserRoles(userId, roleIds, assignedBy); - console.log("✅ User roles updated successfully:", { userId, roleIds }); - res.json({ message: "User roles updated successfully" }); - } catch (error) { - console.error("Error setting user roles:", error); - res.status(500).json({ message: "Failed to update user roles" }); - } - }); - - app.put('/api/users/:userId/roles', isAuthenticated, async (req: any, res) => { - try { - const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - if (!currentUser || currentUser.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const userId = req.params.userId; - const { roleIds } = req.body; - - if (!Array.isArray(roleIds)) { - return res.status(400).json({ message: "roleIds must be an array" }); - } - - const assignedBy = currentUser.id; - await storage.setUserRoles(userId, roleIds, assignedBy); - res.json({ message: "User roles updated successfully" }); - } catch (error) { - console.error("Error setting user roles:", error); - res.status(500).json({ message: "Failed to set user roles" }); - } - }); - - app.get('/api/users/:userId/permissions', 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') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const userId = req.params.userId; - const permissions = await storage.getUserEffectivePermissions(userId); - res.json(permissions); - } catch (error) { - console.error("Error fetching user permissions:", error); - res.status(500).json({ message: "Failed to fetch user permissions" }); - } - }); - - // Permission checking routes - app.get('/api/users/:userId/has-permission/:permissionName', isAuthenticated, async (req: any, res) => { - try { - const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - const userId = req.params.userId; - const permissionName = req.params.permissionName; - - // Users can check their own permissions, admins can check anyone's - if (currentUser?.id !== userId && currentUser?.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const hasPermission = await storage.userHasPermission(userId, permissionName); - res.json({ hasPermission }); - } catch (error) { - console.error("Error checking user permission:", error); - res.status(500).json({ message: "Failed to check permission" }); - } - }); - - app.get('/api/users/:userId/has-role/:roleName', isAuthenticated, async (req: any, res) => { - try { - const currentUser = await storage.getUser(req.user.claims ? req.user.claims.sub : req.user.id); - const userId = req.params.userId; - const roleName = req.params.roleName; - - // Users can check their own roles, admins can check anyone's - if (currentUser?.id !== userId && currentUser?.role !== 'admin') { - return res.status(403).json({ message: "Insufficient permissions" }); - } - - const hasRole = await storage.userHasRole(userId, roleName); - res.json({ hasRole }); - } catch (error) { - console.error("Error checking user role:", error); - res.status(500).json({ message: "Failed to check role" }); - } - }); - - // DLC Products routes - app.get('/api/dlc-products', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - const { status, supplierId } = req.query; - - // Determine which groups to filter by - let groupIds: number[] = []; - if (user.role === 'admin') { - // Admin can specify a store or see all - if (req.query.storeId) { - groupIds = [parseInt(req.query.storeId)]; - } - // If no storeId specified, admin sees all (don't filter by groupIds) - } else { - // Non-admin users see only their assigned groups - groupIds = user.userGroups.map(ug => ug.group.id); - } - - const filters: { status?: string; supplierId?: number; } = {}; - if (status) filters.status = status; - if (supplierId) filters.supplierId = parseInt(supplierId); - - console.log('🔍 DLC Products API called with:', { - userId, - userRole: user.role, - userGroups: user.userGroups?.map(ug => ({groupId: ug.group.id, groupName: ug.group.name})), - groupIds: user.role === 'admin' && !req.query.storeId ? 'all' : groupIds, - filters, - queryStoreId: req.query.storeId - }); - - const dlcProducts = await storage.getDlcProducts( - user.role === 'admin' && !req.query.storeId ? undefined : groupIds, - filters - ); - - console.log('📋 DLC Products returned:', dlcProducts.length, 'items'); - if (dlcProducts.length > 0) { - console.log('📋 Sample DLC products groupIds:', dlcProducts.slice(0, 3).map(p => ({id: p.id, name: p.productName, groupId: p.groupId}))); - } - res.json(dlcProducts); - } catch (error) { - console.error("Error fetching DLC products:", error); - res.status(500).json({ message: "Failed to fetch DLC products" }); - } - }); - - app.get('/api/dlc-products/stats', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // Determine which groups to filter by - let groupIds: number[] = []; - if (user.role === 'admin') { - if (req.query.storeId) { - groupIds = [parseInt(req.query.storeId)]; - } - } else { - groupIds = user.userGroups.map(ug => ug.group.id); - } - - const stats = await storage.getDlcStats( - user.role === 'admin' && !req.query.storeId ? undefined : groupIds - ); - - res.json(stats); - } catch (error) { - console.error("Error fetching DLC stats:", error); - res.status(500).json({ message: "Failed to fetch DLC stats" }); - } - }); - - app.get('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const dlcProduct = await storage.getDlcProduct(id); - - if (!dlcProduct) { - return res.status(404).json({ message: "DLC Product not found" }); - } - - // Check if user has access to this product's group - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(dlcProduct.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - res.json(dlcProduct); - } catch (error) { - console.error("Error fetching DLC product:", error); - res.status(500).json({ message: "Failed to fetch DLC product" }); - } - }); - - app.post('/api/dlc-products', isAuthenticated, async (req: any, res) => { - try { - console.log('📨 POST /api/dlc-products - Request body:', JSON.stringify(req.body, null, 2)); - - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - // REMOVED: All role restrictions - tous les rôles peuvent créer des DLC - console.log("🔍 Creating DLC product - no role restrictions:", { - userId, - userRole: user.role, - requestGroupId: req.body.groupId, - userGroups: user.userGroups?.map(ug => ({groupId: ug.group.id, groupName: ug.group.name})) - }); - - // Fix groupId if missing - use user's assigned group or fallback - let finalGroupId = req.body.groupId; - - console.log("🔍 DLC GroupId Debug Production:", { - originalGroupId: req.body.groupId, - userGroups: user.userGroups?.map(ug => ({ - groupId: ug.groupId, - groupName: ug.group?.name, - rawGroup: ug.group - })), - userGroupsLength: user.userGroups?.length, - firstUserGroup: user.userGroups?.[0] - }); - - if (!finalGroupId || finalGroupId === undefined || finalGroupId === null) { - if (user.userGroups?.[0]?.groupId) { - finalGroupId = user.userGroups[0].groupId; - console.log("🔧 DLC Backend Fix: Using user's assigned group:", finalGroupId); - } else if (user.userGroups?.[0]?.group?.id) { - // Alternative access pattern for production - finalGroupId = user.userGroups[0].group.id; - console.log("🔧 DLC Backend Fix: Using user's group.id:", finalGroupId); - } else { - finalGroupId = 1; // Emergency fallback - console.log("🚨 DLC Backend Fix: Using emergency fallback groupId:", finalGroupId); - } - } - - console.log("🔍 Pre-validation data:", { - body: req.body, - userId, - userGroups: user.userGroups?.map(ug => ({groupId: ug.groupId, groupName: ug.group?.name})), - originalGroupId: req.body.groupId, - finalGroupId, - combined: { ...req.body, createdBy: userId, groupId: finalGroupId } - }); - - const dataToValidate = { - ...req.body, - createdBy: userId, - groupId: finalGroupId, - }; - - console.log("🔍 PRODUCTION DEBUG - Data before validation:", dataToValidate); - - const validatedData = insertDlcProductFrontendSchema.parse(dataToValidate); - - console.log("✅ PRODUCTION DEBUG - Post-validation data:", validatedData); - console.log("🔍 PRODUCTION DEBUG - GroupId after validation:", validatedData.groupId, typeof validatedData.groupId); - - console.log("🔍 PRODUCTION DEBUG - Calling storage.createDlcProduct with groupId:", validatedData.groupId); - const dlcProduct = await storage.createDlcProduct(validatedData); - console.log("🔍 PRODUCTION DEBUG - DLC created with groupId:", dlcProduct.groupId, "- Should be:", finalGroupId); - console.log("📦 Raw returned DLC product:", dlcProduct); - console.log('✅ DLC Product created successfully:', { - id: dlcProduct.id, - productName: dlcProduct.productName, - groupId: dlcProduct.groupId, - createdBy: dlcProduct.createdBy - }); - - res.status(201).json(dlcProduct); - } catch (error) { - console.error("❌ Error creating DLC product:", error); - if (error instanceof z.ZodError) { - return res.status(400).json({ - message: "Validation error", - errors: error.errors - }); - } - res.status(500).json({ message: "Failed to create DLC product" }); - } - }); - - app.put('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - - // First check if the product exists and user has access - const existingProduct = await storage.getDlcProduct(id); - if (!existingProduct) { - return res.status(404).json({ message: "DLC Product not found" }); - } - - const user = await storage.getUserWithGroups(userId); - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(existingProduct.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - const validatedData = insertDlcProductFrontendSchema.partial().parse(req.body); - const dlcProduct = await storage.updateDlcProduct(id, validatedData); - - res.json(dlcProduct); - } catch (error) { - console.error("Error updating DLC product:", error); - if (error instanceof z.ZodError) { - return res.status(400).json({ - message: "Validation error", - errors: error.errors - }); - } - res.status(500).json({ message: "Failed to update DLC product" }); - } - }); - - app.post('/api/dlc-products/:id/validate', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUser(userId); - - // Permission already checked by middleware - - // Check if the product exists and user has access - const existingProduct = await storage.getDlcProduct(id); - if (!existingProduct) { - return res.status(404).json({ message: "DLC Product not found" }); - } - - if (user.role !== 'admin') { - const userWithGroups = await storage.getUserWithGroups(userId); - const userGroupIds = userWithGroups?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(existingProduct.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - const dlcProduct = await storage.validateDlcProduct(id, userId); - res.json(dlcProduct); - } catch (error) { - console.error("Error validating DLC product:", error); - res.status(500).json({ message: "Failed to validate DLC product" }); - } - }); - - app.delete('/api/dlc-products/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - - // Check if the product exists and user has access - const existingProduct = await storage.getDlcProduct(id); - if (!existingProduct) { - return res.status(404).json({ message: "DLC Product not found" }); - } - - const user = await storage.getUserWithGroups(userId); - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(existingProduct.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - - // Non-admin users can only delete their own products - if (existingProduct.createdBy !== userId) { - return res.status(403).json({ message: "Can only delete your own products" }); - } - } - - await storage.deleteDlcProduct(id); - res.status(204).send(); - } catch (error) { - console.error("Error deleting DLC product:", error); - res.status(500).json({ message: "Failed to delete DLC product" }); - } - }); - - // Tasks routes - app.get('/api/tasks', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - const storeId = req.query.storeId; - - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - let groupIds: number[] | undefined; - if (user.role === 'admin') { - // Admin peut voir toutes les tâches ou filtrer par magasin - if (storeId && storeId !== 'all') { - groupIds = [parseInt(storeId)]; - } - // Si pas de storeId ou storeId='all', pas de filtrage (toutes les tâches) - } else { - // Non-admin voit seulement ses magasins assignés - const userGroupIds = user.userGroups.map(ug => ug.group.id); - if (storeId && storeId !== 'all') { - const requestedStoreId = parseInt(storeId); - if (userGroupIds.includes(requestedStoreId)) { - groupIds = [requestedStoreId]; - } else { - return res.status(403).json({ message: "Access denied to this store" }); - } - } else { - groupIds = userGroupIds; - } - } - - const tasks = await storage.getTasks(groupIds); - res.json(tasks); - } catch (error) { - console.error("Error fetching tasks:", error); - res.status(500).json({ message: "Failed to fetch tasks" }); - } - }); - - app.get('/api/tasks/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - - const task = await storage.getTask(id); - if (!task) { - return res.status(404).json({ message: "Task not found" }); - } - - const user = await storage.getUserWithGroups(userId); - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(task.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - res.json(task); - } catch (error) { - console.error("Error fetching task:", error); - res.status(500).json({ message: "Failed to fetch task" }); - } - }); - - app.post('/api/tasks', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } - - if (user.role !== 'admin') { - const userGroupIds = user.userGroups.map(ug => ug.group.id); - if (!userGroupIds.includes(req.body.groupId)) { - return res.status(403).json({ message: "Access denied to this store" }); - } - } - - const validatedData = insertTaskSchema.parse({ - ...req.body, - createdBy: userId, - }); - - const task = await storage.createTask(validatedData); - res.status(201).json(task); - } catch (error) { - console.error("Error creating task:", error); - if (error instanceof z.ZodError) { - return res.status(400).json({ - message: "Validation error", - errors: error.errors - }); - } - res.status(500).json({ message: "Failed to create task" }); - } - }); - - app.put('/api/tasks/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - - const existingTask = await storage.getTask(id); - if (!existingTask) { - return res.status(404).json({ message: "Task not found" }); - } - - const user = await storage.getUserWithGroups(userId); - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(existingTask.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - } - - const validatedData = insertTaskSchema.partial().parse(req.body); - const task = await storage.updateTask(id, validatedData); - - res.json(task); - } catch (error) { - console.error("Error updating task:", error); - if (error instanceof z.ZodError) { - return res.status(400).json({ - message: "Validation error", - errors: error.errors - }); - } - res.status(500).json({ message: "Failed to update task" }); - } - }); - - app.post('/api/tasks/:id/complete', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - - console.log("🎯 DEV Task completion request:", { id, userId }); - - const existingTask = await storage.getTask(id); - if (!existingTask) { - console.log("❌ Task not found:", id); - return res.status(404).json({ message: "Task not found" }); - } - - console.log("✅ Task found:", existingTask); - - const user = await storage.getUserWithGroups(userId); - console.log("👤 User data:", user); - - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(existingTask.groupId)) { - console.log("❌ Access denied - group mismatch:", { userGroupIds, taskGroupId: existingTask.groupId }); - return res.status(403).json({ message: "Access denied" }); - } - } - - console.log("🔄 Completing task using storage.completeTask..."); - await storage.completeTask(id, userId); - const updatedTask = await storage.getTask(id); - console.log("✅ Task completed successfully:", updatedTask); - res.json(updatedTask); - } catch (error) { - console.error("❌ Error completing task:", error); - res.status(500).json({ message: "Failed to complete task", error: (error as Error).message }); - } - }); - - app.delete('/api/tasks/:id', isAuthenticated, async (req: any, res) => { - try { - const id = parseInt(req.params.id); - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - - const existingTask = await storage.getTask(id); - if (!existingTask) { - return res.status(404).json({ message: "Task not found" }); - } - - const user = await storage.getUserWithGroups(userId); - if (user?.role !== 'admin') { - const userGroupIds = user?.userGroups.map(ug => ug.group.id) || []; - if (!userGroupIds.includes(existingTask.groupId)) { - return res.status(403).json({ message: "Access denied" }); - } - - if (existingTask.createdBy !== userId) { - return res.status(403).json({ message: "Can only delete your own tasks" }); - } - } - - await storage.deleteTask(id); - res.status(204).send(); - } catch (error) { - console.error("Error deleting task:", error); - res.status(500).json({ message: "Failed to delete task" }); - } - }); - - // Debug endpoint to check permissions without auth (temporary) - - // Debug endpoint to check current user auth status - app.get('/api/debug/auth-status', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - - res.json({ - isAuthenticated: true, - userId: userId, - user: user ? { - id: user.id, - username: user.username, - email: user.email, - role: user.role, - firstName: user.firstName, - lastName: user.lastName - } : null, - canAccessPermissions: user?.role === 'admin', - timestamp: new Date().toISOString() - }); - } catch (error) { - res.status(500).json({ error: (error as Error).message }); - } - }); - - // Debug endpoint to check task permissions specifically - - // Debug endpoint for permissions status - app.get('/api/debug/permissions-status', isAuthenticated, async (req: any, res) => { - try { - const userId = req.user.claims ? req.user.claims.sub : req.user.id; - const user = await storage.getUserWithGroups(userId); - - res.json({ - user: user ? { - id: user.id, - username: user.username, - role: user.role, - groupCount: user.userGroups.length - } : null, - permissionSystemType: 'hardcoded', - timestamp: new Date().toISOString() - }); - } catch (error) { - console.error('❌ Permissions status error:', error); - res.status(500).json({ error: (error as Error).message }); - } - }); - - // ENDPOINT TEMPORAIRE - Test production storage avec gestion d'erreur détaillée - - - - // Debug/Fix endpoint for production permission issues - + // Create server instance const httpServer = createServer(app); + + // Server startup return httpServer; } diff --git a/server/storage.ts b/server/storage.ts index 7480713..a1b9373 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -10,6 +10,8 @@ import { customerOrders, dlcProducts, tasks, + nocodbConfig, + invoiceVerificationCache, type User, type UpsertUser, type Group, @@ -42,6 +44,10 @@ import { type DlcProductWithRelations, type Task, type InsertTask, + type NocodbConfig, + type InsertNocodbConfig, + type InvoiceVerificationCache, + type InsertInvoiceVerificationCache, } from "@shared/schema"; import { db } from "./db"; import { eq, and, inArray, desc, sql, gte, lte } from "drizzle-orm"; @@ -120,10 +126,16 @@ export interface IStorage { // NocoDB Configuration operations getNocodbConfigs(): Promise; getNocodbConfig(id: number): Promise; + getActiveNocodbConfig(): Promise; createNocodbConfig(config: InsertNocodbConfig): Promise; updateNocodbConfig(id: number, config: Partial): Promise; deleteNocodbConfig(id: number): Promise; + // Invoice Verification Cache operations + getInvoiceVerificationCache(cacheKey: string): Promise; + createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise; + clearExpiredCache(): Promise; + // Customer Order operations getCustomerOrders(groupIds?: number[]): Promise; getCustomerOrder(id: number): Promise; @@ -1104,6 +1116,26 @@ export class DatabaseStorage implements IStorage { await db.delete(nocodbConfig).where(eq(nocodbConfig.id, id)); } + async getActiveNocodbConfig(): Promise { + const [config] = await db.select().from(nocodbConfig).where(eq(nocodbConfig.isActive, true)); + return config; + } + + // Invoice Verification Cache operations + async getInvoiceVerificationCache(cacheKey: string): Promise { + const [cache] = await db.select().from(invoiceVerificationCache).where(eq(invoiceVerificationCache.cacheKey, cacheKey)); + return cache; + } + + async createInvoiceVerificationCache(cache: InsertInvoiceVerificationCache): Promise { + const [newCache] = await db.insert(invoiceVerificationCache).values(cache).returning(); + return newCache; + } + + async clearExpiredCache(): Promise { + await db.delete(invoiceVerificationCache).where(sql`${invoiceVerificationCache.expiresAt} < NOW()`); + } + // Customer Order operations async getCustomerOrders(groupIds?: number[]): Promise { try { diff --git a/shared/schema.ts b/shared/schema.ts index 12f0606..b3b7a22 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -141,6 +141,37 @@ export const publicityParticipations = pgTable("publicity_participations", { pk: primaryKey({ columns: [table.publicityId, table.groupId] }) })); +// Configuration NocoDB globale pour la vérification des factures +export const nocodbConfig = pgTable("nocodb_config", { + id: serial("id").primaryKey(), + name: varchar("name").notNull(), + baseUrl: varchar("base_url").notNull(), + projectId: varchar("project_id").notNull(), + apiToken: varchar("api_token").notNull(), + description: text("description"), + isActive: boolean("is_active").default(true), + createdBy: varchar("created_by"), + createdAt: timestamp("created_at").defaultNow(), + updatedAt: timestamp("updated_at").defaultNow(), +}); + +// Cache de vérification des factures +export const invoiceVerificationCache = pgTable("invoice_verification_cache", { + id: serial("id").primaryKey(), + cacheKey: varchar("cache_key", { length: 255 }).notNull(), + groupId: integer("group_id").notNull(), + invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(), + supplierName: varchar("supplier_name", { length: 255 }), + exists: boolean("exists").notNull(), + matchType: varchar("match_type", { length: 50 }).notNull(), + errorMessage: text("error_message"), + cacheHit: boolean("cache_hit").default(false), + apiCallTime: integer("api_call_time"), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow(), + updatedAt: timestamp("updated_at").defaultNow(), +}); + @@ -472,6 +503,21 @@ export const insertTaskSchema = createInsertSchema(tasks).omit({ dueDate: z.coerce.date().optional().nullable(), // Convertit automatiquement les chaînes en Date }); +export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + +export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + +export type InsertNocodbConfig = z.infer; +export type InsertInvoiceVerificationCache = z.infer; + // Types export type User = typeof users.$inferSelect; export type UpsertUser = z.infer; @@ -586,6 +632,9 @@ export type PublicityWithRelations = Publicity & { export type NocodbConfig = typeof nocodbConfig.$inferSelect; export type InsertNocodbConfig = z.infer; +export type InvoiceVerificationCache = typeof invoiceVerificationCache.$inferSelect; +export type InsertInvoiceVerificationCache = z.infer; + export type CustomerOrder = typeof customerOrders.$inferSelect; export type InsertCustomerOrder = z.infer;