Files
LogiFlow/server/dateUtils.ts
MichaelandClaude Opus 5.5 6f1a39e4b2 feat(api): API externe de rapprochement BL / factures
Nouvelle API sous /api/ext/v1, authentifiée par clé (EXTERNAL_API_KEYS,
en-tête X-API-Key ou Authorization: Bearer), pour qu'un outil tiers
lise magasins, fournisseurs et livraisons livrées (n° BL) puis écrive
la référence, les montants et l'échéance de la facture, avec
validation/dévalidation optionnelle du rapprochement.

- normalizeDateString extrait dans server/dateUtils.ts pour être partagé
- /api/ext/ exempté du CSRF (pas de cookie de session)
- EXTERNAL_API_KEYS ajouté à .env.example et docker-compose.yml
- documentation : docs/API-RAPPROCHEMENT.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:14:45 +02:00

74 lines
2.8 KiB
TypeScript

// Normalisation des dates d'échéance renvoyées par NocoDB (formats variés)
// vers YYYY-MM-DD. Partagée par les routes web et l'API externe.
export function normalizeDateString(dateString: string | null | undefined): string | null {
if (!dateString || typeof dateString !== 'string') return null;
const trimmed = dateString.trim();
if (!trimmed) return null;
try {
// Si déjà au format ISO (YYYY-MM-DD), le retourner tel quel
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
return trimmed;
}
// Format slash ou tiret : DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, MM-DD-YYYY
const slashMatch = trimmed.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})$/);
if (slashMatch) {
const [, first, second, year] = slashMatch;
const firstNum = parseInt(first);
const secondNum = parseInt(second);
// Validation basique des valeurs
if (firstNum > 31 || secondNum > 31 || firstNum === 0 || secondNum === 0) {
console.warn(`⚠️ Date invalide (valeurs hors limites): ${trimmed}`);
return null;
}
// Déterminer le format en fonction des valeurs
let day: string, month: string;
if (firstNum > 12) {
// first > 12 → forcément DD/MM (format français/européen)
day = first.padStart(2, '0');
month = second.padStart(2, '0');
} else if (secondNum > 12) {
// second > 12 → forcément MM/DD (format américain)
day = second.padStart(2, '0');
month = first.padStart(2, '0');
} else {
// Ambiguïté (les deux < 12) → on assume format français DD/MM par défaut
// Pour être plus sûr, on pourrait vérifier la configuration du groupe/locale
day = first.padStart(2, '0');
month = second.padStart(2, '0');
}
// Validation finale : mois entre 1-12, jour entre 1-31
const monthNum = parseInt(month);
const dayNum = parseInt(day);
if (monthNum < 1 || monthNum > 12 || dayNum < 1 || dayNum > 31) {
console.warn(`⚠️ Date invalide après parsing: ${trimmed} → month=${month}, day=${day}`);
return null;
}
return `${year}-${month}-${day}`;
}
// Essayer de parser avec Date (format ISO complet avec heures)
const date = new Date(trimmed);
if (!isNaN(date.getTime())) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Si aucun format reconnu, retourner null (ne pas persister une date invalide)
console.warn(`⚠️ Format de date non reconnu: ${trimmed}`);
return null;
} catch (error) {
console.error('❌ Erreur normalisation date:', error);
return null; // Retourner null en cas d'erreur pour éviter de persister des données invalides
}
}