|
|
@@ -82,6 +82,59 @@ function detectCategory(text) {
|
|
|
return 'autre';
|
|
|
}
|
|
|
|
|
|
+// Date patterns for French invoices (dd/mm/yyyy, dd-mm-yyyy, dd.mm.yyyy, "15 janvier 2024", etc.)
|
|
|
+const DATE_PATTERNS = [
|
|
|
+ // "Date de facture : 15/03/2024" or "Date : 15/03/2024"
|
|
|
+ /(?:date\s*(?:de\s*)?(?:facture|facturation|émission|[eé]mission))\s*[:\s]*(\d{1,2})[/.\-](\d{1,2})[/.\-](20\d{2})/i,
|
|
|
+ /(?:date)\s*[:\s]*(\d{1,2})[/.\-](\d{1,2})[/.\-](20\d{2})/i,
|
|
|
+ // "Facturé le 15/03/2024" or "Émise le 15/03/2024"
|
|
|
+ /(?:factur[eé]e?\s*le|[eé]mise?\s*le|le)\s*(\d{1,2})[/.\-](\d{1,2})[/.\-](20\d{2})/i,
|
|
|
+ // French month names: "15 mars 2024", "1er janvier 2024"
|
|
|
+ /(\d{1,2})(?:er)?\s+(janvier|f[eé]vrier|mars|avril|mai|juin|juillet|ao[uû]t|septembre|octobre|novembre|d[eé]cembre)\s+(20\d{2})/i,
|
|
|
+ // Standalone dd/mm/yyyy near top of document (fallback)
|
|
|
+ /(\d{1,2})[/.\-](\d{1,2})[/.\-](20\d{2})/,
|
|
|
+];
|
|
|
+
|
|
|
+const FRENCH_MONTHS = {
|
|
|
+ 'janvier': '01', 'février': '02', 'fevrier': '02',
|
|
|
+ 'mars': '03', 'avril': '04', 'mai': '05', 'juin': '06',
|
|
|
+ 'juillet': '07', 'août': '08', 'aout': '08',
|
|
|
+ 'septembre': '09', 'octobre': '10', 'novembre': '11',
|
|
|
+ 'décembre': '12', 'decembre': '12',
|
|
|
+};
|
|
|
+
|
|
|
+function detectDate(text) {
|
|
|
+ for (const pattern of DATE_PATTERNS) {
|
|
|
+ const match = text.match(pattern);
|
|
|
+ if (!match) continue;
|
|
|
+
|
|
|
+ let day, month, year;
|
|
|
+
|
|
|
+ // Check if it's a French month name pattern
|
|
|
+ const monthName = match[2]?.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
|
+ const monthLookup = Object.entries(FRENCH_MONTHS).find(([k]) =>
|
|
|
+ k.normalize('NFD').replace(/[\u0300-\u036f]/g, '') === monthName
|
|
|
+ );
|
|
|
+
|
|
|
+ if (monthLookup) {
|
|
|
+ day = match[1].padStart(2, '0');
|
|
|
+ month = monthLookup[1];
|
|
|
+ year = match[3];
|
|
|
+ } else {
|
|
|
+ day = match[1].padStart(2, '0');
|
|
|
+ month = match[2].padStart(2, '0');
|
|
|
+ year = match[3];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validate
|
|
|
+ const d = parseInt(day), m = parseInt(month), y = parseInt(year);
|
|
|
+ if (d < 1 || d > 31 || m < 1 || m > 12 || y < 2000 || y > 2099) continue;
|
|
|
+
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+}
|
|
|
+
|
|
|
function detectAmount(text) {
|
|
|
for (const pattern of AMOUNT_PATTERNS) {
|
|
|
const match = text.match(pattern);
|
|
|
@@ -119,6 +172,7 @@ async function analyzePdf(filePath) {
|
|
|
const detectedAmount = detectAmount(text);
|
|
|
const detectedCategory = supplierInfo?.category || detectCategory(text);
|
|
|
const detectedSupplier = supplierInfo?.name || null;
|
|
|
+ const detectedDate = detectDate(text);
|
|
|
|
|
|
// Try to build a label
|
|
|
let label = '';
|
|
|
@@ -135,6 +189,7 @@ async function analyzePdf(filePath) {
|
|
|
detected_category: detectedCategory,
|
|
|
detected_amount: detectedAmount,
|
|
|
detected_label: label,
|
|
|
+ detected_date: detectedDate,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
@@ -156,7 +211,8 @@ function analyzeFromFilename(filePath) {
|
|
|
detected_category: detectedCategory,
|
|
|
detected_amount: detectedAmount,
|
|
|
detected_label: label,
|
|
|
+ detected_date: null,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-module.exports = { analyzeFile, detectSupplier, detectCategory, detectAmount, SUPPLIERS };
|
|
|
+module.exports = { analyzeFile, detectSupplier, detectCategory, detectAmount, detectDate, SUPPLIERS };
|