You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 line
9.3KB

  1. using Services.ReActAgent;
  2. using System.Text;
  3. using System.Text.RegularExpressions;
  4. using ToolsServices;
  5. namespace Services
  6. {
  7. public static class FactureService
  8. {
  9. #region Variables
  10. private static readonly string NomFichierParametres = FichiersInternesService.ParamsFactures;
  11. #endregion
  12. #region Méthodes publiques
  13. public static async Task<(bool, string)> Traitement(string inputPath, string outputPath, bool isApiExterne)
  14. {
  15. StringBuilder sb = new();
  16. LoggerService.LogInfo($"FactureService.Traitement");
  17. if (!Directory.Exists(inputPath))
  18. {
  19. LoggerService.LogWarning($"Le dossier {inputPath} n'existe pas.");
  20. return (false, $"Le dossier {inputPath} n'existe pas.");
  21. }
  22. if (!Directory.Exists(outputPath))
  23. {
  24. LoggerService.LogWarning($"Le dossier {outputPath} n'existe pas.");
  25. return (false, $"Le dossier {outputPath} n'existe pas.");
  26. }
  27. var fichiers = Directory
  28. .EnumerateFiles(inputPath, "*.*", SearchOption.AllDirectories)
  29. .Where(f => f.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
  30. .ToArray();
  31. if (fichiers.Length == 0)
  32. {
  33. LoggerService.LogWarning($"Aucun fichier trouvé dans le dossier spécifié : {inputPath}");
  34. return (false, $"Aucun fichier trouvé dans le dossier spécifié : {inputPath}");
  35. }
  36. var reActRagAgent = new ReActAgent.ReActAgent();
  37. var isOk = true;
  38. foreach (var fichier in fichiers)
  39. {
  40. try
  41. {
  42. #region Analyse de la facture
  43. var facturePDF = FilesService.ExtractText(fichier);
  44. //facturePDF = CleanPdfText(facturePDF);
  45. //facturePDF = SegmentInvoice(facturePDF);
  46. var prompt = PromptService.GetPrompt(PromptService.ePrompt.FactureService_AnalyserFacture_Extract, facturePDF);
  47. LoggerService.LogInfo($"Traitement de facture PDF to XML : {fichier}");
  48. var (reponse,m) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.Facture_To_Xml, true, prompt, "Traitement de facture PDF to XML", "", isApiExterne);
  49. if (!XmlService.IsXML(reponse))
  50. {
  51. sb.AppendLine($"Le résultat n'est pas au format XML pour le fichier : {fichier}");
  52. LoggerService.LogWarning($"Le résultat n'est pas au format XML pour le fichier : {fichier}");
  53. string fileNameXML = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(fichier) + ".xml");
  54. TxtService.CreateTextFile(fileNameXML, reponse);
  55. }
  56. else
  57. {
  58. #region Sauvegarde du résultat
  59. LoggerService.LogDebug($"Le résultat est au format XML pour le fichier : {fichier}");
  60. string fileNameXML = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(fichier) + ".xml");
  61. TxtService.CreateTextFile(fileNameXML, reponse);
  62. #endregion
  63. }
  64. #endregion
  65. }
  66. catch (Exception ex)
  67. {
  68. isOk = false;
  69. LoggerService.LogError($"Erreur lors de l'extraction du texte de {fichier} : {ex.Message}");
  70. }
  71. }
  72. LoggerService.LogInfo($"Traitement des factures PDF to XML terminée");
  73. return (isOk, sb.ToString());
  74. }
  75. public static (string, string) LoadParametres()
  76. {
  77. LoggerService.LogInfo("FactureService.LoadParametres");
  78. //ParametresOllamaService SelectedItem = new();
  79. try
  80. {
  81. string FicheMission = "";
  82. string PathCV = "";
  83. if (File.Exists(NomFichierParametres))
  84. {
  85. string[] lignes = File.ReadAllLines(NomFichierParametres);
  86. if (lignes.Length > 0)
  87. FicheMission = lignes[0];
  88. if (lignes.Length > 1)
  89. PathCV = lignes[1];
  90. }
  91. return (FicheMission, PathCV);
  92. }
  93. catch
  94. {
  95. LoggerService.LogError($"Erreur lors du chargement des paramètres depuis {NomFichierParametres}");
  96. return ("", "");
  97. }
  98. }
  99. public static bool SaveParametres(string inputPath, string outputPath)
  100. {
  101. LoggerService.LogInfo("FactureService.SaveParametres");
  102. try
  103. {
  104. StringBuilder sb = new();
  105. sb.AppendLine(inputPath);
  106. sb.AppendLine(outputPath);
  107. File.WriteAllText(NomFichierParametres, sb.ToString());
  108. return true;
  109. }
  110. catch
  111. {
  112. LoggerService.LogError($"Erreur lors de la sauvegarde des paramètres dans {NomFichierParametres}");
  113. return false;
  114. }
  115. }
  116. #endregion
  117. #region Méthodes privées
  118. private static string CleanPdfText(string rawText)
  119. {
  120. var text = rawText;
  121. // Normaliser les fins de ligne
  122. text = text.Replace("\r", "\n");
  123. // Supprimer les caractères de contrôle (sauf \n)
  124. text = new string(text.Where(c => !char.IsControl(c) || c == '\n').ToArray());
  125. // Supprimer les en-têtes et pieds de page typiques (ex: "Page 1/3", "Facture ACME", etc.)
  126. text = Regex.Replace(text, @"Page\s+\d+(/\d+)?", "", RegexOptions.IgnoreCase);
  127. text = Regex.Replace(text, @"Facture\s+n[°º]\s*\d+", "", RegexOptions.IgnoreCase);
  128. // Uniformiser les espaces : tabulations → espace, et espaces multiples → 1 seul
  129. text = Regex.Replace(text, @"[ \t]+", " ");
  130. // Supprimer les lignes vides excessives
  131. text = Regex.Replace(text, @"\n{2,}", "\n");
  132. // Supprimer les traits de séparation (souvent des "-----" ou "=====")
  133. text = Regex.Replace(text, @"[-=]{3,}", "");
  134. // Corriger les montants : ex "1 234,56 €" → "1234.56 EUR"
  135. text = Regex.Replace(text, @"(\d{1,3}(?:[ \u00A0]\d{3})*,\d{2}) ?€",
  136. m => m.Groups[1].Value.Replace(" ", "").Replace("\u00A0", "").Replace(",", ".") + " EUR");
  137. // Corriger les dates : ex "01-02-2025" → "2025-02-01"
  138. text = Regex.Replace(text, @"\b(\d{2})[-/](\d{2})[-/](\d{4})\b", "$3-$2-$1");
  139. // Fusionner les lignes coupées artificiellement (ex: "TOTAL\n123,45" → "TOTAL 123,45")
  140. text = Regex.Replace(text, @"([A-Za-z])\n(\d)", "$1 $2");
  141. // Supprimer les espaces en trop au début et fin
  142. text = text.Trim();
  143. return text;
  144. }
  145. private static string SegmentInvoice(string text)
  146. {
  147. var sb = new StringBuilder();
  148. // Bloc fournisseur
  149. var fournisseurMatch = Regex.Match(text, @"(?i)(?:fournisseur|vendor|seller).{0,50}\n(.+?)(\n|$)");
  150. if (fournisseurMatch.Success)
  151. {
  152. sb.AppendLine("=== FOURNISSEUR ===");
  153. sb.AppendLine(fournisseurMatch.Groups[1].Value.Trim());
  154. }
  155. // Bloc client
  156. var clientMatch = Regex.Match(text, @"(?i)(?:client|acheteur|buyer|bill to|destinataire).{0,50}\n(.+?)(\n|$)");
  157. if (clientMatch.Success)
  158. {
  159. sb.AppendLine("\n=== CLIENT ===");
  160. sb.AppendLine(clientMatch.Groups[1].Value.Trim());
  161. }
  162. // Numéro de facture
  163. var numeroMatch = Regex.Match(text, @"(?i)facture[^\d]*(\d+)");
  164. if (numeroMatch.Success)
  165. {
  166. sb.AppendLine("\n=== NUMERO FACTURE ===");
  167. sb.AppendLine(numeroMatch.Groups[1].Value.Trim());
  168. }
  169. // Date de facture
  170. var dateMatch = Regex.Match(text, @"(?i)(?:date|issued|emission)[^\d]*(\d{4}-\d{2}-\d{2})");
  171. if (dateMatch.Success)
  172. {
  173. sb.AppendLine("\n=== DATE FACTURE ===");
  174. sb.AppendLine(dateMatch.Groups[1].Value.Trim());
  175. }
  176. // Bloc lignes de facture (simplifié : cherche un tableau prix/quantité)
  177. var lignesMatch = Regex.Match(text, @"(?is)(description.*?total)", RegexOptions.IgnoreCase);
  178. if (lignesMatch.Success)
  179. {
  180. sb.AppendLine("\n=== LIGNES FACTURE ===");
  181. sb.AppendLine(lignesMatch.Groups[0].Value.Trim());
  182. }
  183. // Total général
  184. var totalMatch = Regex.Match(text, @"(?i)(total (?:ttc|general|amount)).{0,10}([\d.,]+ ?eur)");
  185. if (totalMatch.Success)
  186. {
  187. sb.AppendLine("\n=== TOTAL GENERAL ===");
  188. sb.AppendLine(totalMatch.Groups[2].Value.Trim());
  189. }
  190. // Retour du texte segmenté
  191. return sb.Length > 0 ? sb.ToString() : text;
  192. }
  193. #endregion
  194. }
  195. }