merge(main): resolve conflicts keeping codefou + email on suppliers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Sonnet 4.6 committed 2026-05-15 11:14:35 +02:00
commit b8ecacee3b
20 files changed
+719 -299

No files matched your search

+11
View File
@@ -36,6 +36,17 @@ export async function runMigrations() {
// Ajouter les migrations hardcodées directement dans le code pour éviter les problèmes de chemins
const hardcodedMigrations = [
{
filename: '20260402000002_add_notified_fields_to_customer_orders.sql',
content: `
ALTER TABLE customer_orders ADD COLUMN IF NOT EXISTS notified_at TIMESTAMP;
ALTER TABLE customer_orders ADD COLUMN IF NOT EXISTS notified_comment TEXT;
`
},
{
filename: '20260402000000_add_codefou_to_suppliers.sql',
content: `ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS codefou VARCHAR;`
},
{
filename: '20250903141000_create_webhook_bap_config.sql',
content: `CREATE TABLE IF NOT EXISTS webhook_bap_config (
+56 -20
View File
@@ -6,7 +6,8 @@ import { requireModulePermission, requireAdmin, requirePermission } from "./perm
import { db, pool } from "./db";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const FormData = require('form-data');
// Suppression de l'import form-data car nous utilisons le FormData natif de Node.js 18+
// const FormData = require('form-data');
console.log('🔍 Using development storage and authentication');
@@ -699,7 +700,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
return res.status(400).json({ error: 'Données manquantes: pdfBase64, fileName ou recipient' });
}
if (!['Prissela', 'Jeremy'].includes(recipient)) {
if (!['Laurie', 'Jeremy'].includes(recipient)) {
return res.status(400).json({ error: 'Destinataire invalide' });
}
@@ -862,8 +863,10 @@ export async function registerRoutes(app: Express): Promise<Server> {
// Parser le multipart/form-data manuellement
const contentType = req.headers['content-type'] || '';
const boundaryMatch = contentType.match(/boundary=(.+)$/);
// Gestion plus robuste du boundary (avec ou sans guillemets)
const boundaryMatch = contentType.match(/boundary="?([^";]+)"?/i);
if (!boundaryMatch) {
console.error('❌ INVOICE PROXY: Boundary manquant dans Content-Type', contentType);
return res.status(400).json({ error: 'Format multipart invalide' });
}
@@ -890,32 +893,35 @@ export async function registerRoutes(app: Express): Promise<Server> {
size: parts.file.buffer.length
});
// Import dynamique standard pour ESM (sans eval)
console.log('🔍 INVOICE PROXY: FormData loaded', {
type: typeof FormData,
isConstructor: typeof FormData === 'function'
});
// Utilisation du FormData natif de Node.js (global)
// On utilise Blob pour transformer le Buffer en objet compatible
const formData = new FormData();
formData.append('file', parts.file.buffer, {
filename: parts.file.filename,
contentType: parts.file.contentType
});
const fileBlob = new Blob([parts.file.buffer], { type: parts.file.contentType });
formData.append('file', fileBlob, parts.file.filename);
formData.append('supplier', parts.supplier || '');
formData.append('blNumber', parts.blNumber || '');
formData.append('type', parts.type || 'Facture');
console.log('🔗 INVOICE PROXY: Calling webhook URL:', parts.webhookUrl);
// En utilisant le FormData natif avec fetch, pas besoin de headers manuels (boundary géré automatiquement)
const response = await fetch(parts.webhookUrl, {
method: 'POST',
body: formData as any,
headers: formData.getHeaders()
body: formData as any
});
if (!response.ok) {
console.error('❌ INVOICE PROXY: Webhook error', response.status);
return res.status(500).json({ error: `Webhook error: ${response.status}` });
const errorBody = await response.text().catch(() => 'No body');
console.error('❌ INVOICE PROXY: Webhook error', {
status: response.status,
statusText: response.statusText,
body: errorBody
});
return res.status(500).json({
error: `Webhook error: ${response.status}`,
details: errorBody
});
}
console.log('✅ INVOICE PROXY: Success');
@@ -3423,9 +3429,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
}
const updatedOrder = await storage.markClientCalled(id, user.id);
console.log(`📞 Client marked as called: order ${id} by user ${user.id} (${user.role})`);
const { comment } = req.body;
const updatedOrder = await storage.markClientCalled(id, user.id, comment);
res.json(updatedOrder);
} catch (error) {
console.error("Error marking client as called:", error);
@@ -6102,6 +6107,37 @@ RÉSUMÉ DU SCAN
}
});
// Proxy API ffnancy - évite les problèmes CORS côté navigateur
app.get('/api/ffnancy/articles', requireAuth, async (req: Request, res: Response) => {
try {
const params = new URLSearchParams();
const allowed = ['search', 'codein', 'ean', 'codefou', 'actif', 'page', 'limit'];
for (const key of allowed) {
if (req.query[key]) params.append(key, req.query[key] as string);
}
const response = await fetch(`https://api.ffnancy.fr/api/articles?${params}`);
if (!response.ok) return res.status(response.status).json({ error: 'API ffnancy error' });
res.json(await response.json());
} catch {
res.status(500).json({ error: 'Failed to reach API ffnancy' });
}
});
app.get('/api/ffnancy/mouvements/entrees', requireAuth, async (req: Request, res: Response) => {
try {
const params = new URLSearchParams();
const allowed = ['artNoId', 'dateDebut', 'dateFin', 'site', 'page', 'limit'];
for (const key of allowed) {
if (req.query[key]) params.append(key, req.query[key] as string);
}
const response = await fetch(`https://api.ffnancy.fr/api/mouvements/entrees?${params}`);
if (!response.ok) return res.status(response.status).json({ error: 'API ffnancy error' });
res.json(await response.json());
} catch {
res.status(500).json({ error: 'Failed to reach API ffnancy' });
}
});
// Create server instance
const httpServer = createServer(app);
+3 -1
View File
@@ -1883,11 +1883,13 @@ export class DatabaseStorage implements IStorage {
}));
}
async markClientCalled(customerOrderId: number, calledBy: string): Promise<CustomerOrder> {
async markClientCalled(customerOrderId: number, calledBy: string, comment?: string): Promise<CustomerOrder> {
const [updated] = await db
.update(customerOrders)
.set({
customerNotified: true,
notifiedAt: new Date(),
notifiedComment: comment || null,
updatedAt: new Date()
})
.where(eq(customerOrders.id, customerOrderId))