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;