using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using MercatorApi;
using MercatorUi;
using MercatorExtensions;
using MercatorDatabase;
using MercatorAI;
using MercatorExtensions;
namespace Action
{
public class Customizer : MercatorUi.ICustomizers.IActionEngineCreated, MercatorUi.ICustomizers.IActionEngineClosed
{
public void ActionEngineCreated(MercatorUi.Engine.Crm.ActionEngine actionEngine)
{
actionEngine.BeforeSave += actionEngine_BeforeSave;
}
public void ActionEngineClosed(MercatorUi.Engine.Crm.ActionEngine actionEngine)
{
actionEngine.BeforeSave -= actionEngine_BeforeSave;
}
void actionEngine_BeforeSave(object sender, MercatorUi.Engine.Crm.ActionEngine.BeforeSaveEventArgs e)
{
MercatorUi.Engine.Crm.ActionEngine actionEngine = (MercatorUi.Engine.Crm.ActionEngine)sender;
if (MercatorUi.Globals.IsMercatorPenguinServer)
{
if (string.IsNullOrWhiteSpace(actionEngine.ActionsRecord.NOTE))
{
actionEngine.LastError = _Divers.Iif_langue(Globals.Langue, "Erläuterung fehlt!", "Missing explanatory note!", "De verklarende notitie ontbreekt!", "Note explicative manquante !");
e.CancelSave = true;
return;
}
if ((actionEngine.ActionsRecord.IMG_BIN == null) || (actionEngine.ActionsRecord.IMG_BIN.Length == 0))
{
actionEngine.LastError = _Divers.Iif_langue(Globals.Langue, "Fehlender Beleg!", "Missing supporting document!", "Ontbrekend bewijsstuk!", "Pièce justificative manquante !");
e.CancelSave = true;
return;
}
MercatorAI.FactoryOpenAI factoryOpenAI = new MercatorAI.FactoryOpenAI();
factoryOpenAI.PluginFullPath = MercatorUi.Globals.MainDir + "PluginOpenAI\\\cf5 MercatorAI.OpenAI.dll";
MercatorAI.Interfaces.IOpenAiResponse openAiResponse = factoryOpenAI.CreateOpenAiResponse("gpt-5.1", true, out string error);
if (!string.IsNullOrEmpty(error))
{
actionEngine.LastError = "Impossible d'initialiser le chat d'OpenAI : " + error;
Globals.ApiLogDelegate.Invoke(actionEngine.LastError);
e.CancelSave = true;
return;
}
string systemMessage = _Divers.Iif_langue(Globals.Langue,
"Du bist ein Assistent, der Spesenbelege für die Buchhaltung unseres Unternehmens liest. Du hältst dich strikt an den ISO-Währungscode. Wenn die Währung nicht erkannt werden kann, gibst du EUR an.",
"You are an assistant that reads expense receipts for our company’s accounting. You strictly follow the ISO currency code. If the currency cannot be recognized, you indicate EUR.",
"Je bent een assistent die onkostennota’s leest voor de boekhouding van ons bedrijf. Je respecteert strikt de ISO-code van de munteenheid. Als de munteenheid niet kan worden herkend, geef je EUR aan.",
"Tu es un assistant qui lit des pièces justificatives de frais pour la comptabililité de notre société. Tu respectes strictement le code ISO de la devise. Si la devise ne peut être reconnue, tu indiques EUR.");
string question = _Divers.Iif_langue(Globals.Langue,
"Welchen Gesamtbetrag und welche Währung erkennst du auf dem beigefügten Bild? Wenn das nicht möglich ist, sag es deutlich.",
"What is the total amount and the currency you recognize in the attached image? If that’s not possible, say so clearly.",
"Wat is het totaalbedrag en de valuta die je herkent op de bijgevoegde afbeelding? Als dat niet mogelijk is, zeg dat dan duidelijk.",
"Quel est le total et la devise que tu reconnais dans l'image jointe. Si ce n'est pas possible, tu le dis clairement.");
FactoryOpenAI.InputDescriptor[] inputs =
[
new FactoryOpenAI.InputDescriptor(FactoryOpenAI.InputTypesEnum.Text, question),
new FactoryOpenAI.InputDescriptor(FactoryOpenAI.InputTypesEnum.ImagePngBytes, actionEngine.ActionsRecord.IMG_BIN)
];
NoteFraisDescriptor r;
try
{
r = openAiResponse.Ask<NoteFraisDescriptor>(inputs, "NoteFraisDescriptor", Globals.Langue, systemMessage: systemMessage);
}
catch (Exception ex)
{
actionEngine.LastError = "OpenAI : " + ex.Message;
Globals.ApiLogDelegate.Invoke(actionEngine.LastError);
e.CancelSave = true;
return;
}
if (r.IsRejected)
{
actionEngine.LastError = _Divers.Iif_langue(Globals.Langue, "Der Anhang scheint kein Beleg zu sein!", "The attachment does not appear to be a supporting document!", "De bijlage lijkt geen bewijsstuk te zijn!", "La pièce jointe ne semble pas être une pièce justificative !"
+ (!string.IsNullOrEmpty(r.Comment) ? "\r\n" + r.Comment : ""));
e.CancelSave = true;
return;
}
if (r.Date == null)
{
actionEngine.LastError = _Divers.Iif_langue(Globals.Langue, "Datum kann nicht bestimmt werden!", "Unable to determine the date!", "Datum kan niet worden bepaald!", "Impossible de déterminer la date !"
+ (!string.IsNullOrEmpty(r.Comment) ? "\r\n" + r.Comment : ""));
e.CancelSave = true;
return;
}
actionEngine.ActionsRecord.OBJET = _Divers.Iif_langue(Globals.Langue, "Spesenabrechnung", "Expense report", "Onkostennota", "Note de frais") + " " + Globals.CurrentUserRecord.NOM + " " + DateTime.Now.ToShortDateShortTimeString();
actionEngine.ActionsRecord.RESULTAT = r.Comment ?? "";
actionEngine.ActionsRecord.DATE = r.Date.Value.Date;
actionEngine.ActionsRecord.MOMENT_1 = DateTime.Now;
actionEngine.ActionsRecord.TOTAL = r.Total;
actionEngine.ActionsRecord.IS_RESTO = r.IsResto;
actionEngine.ActionsRecord.DEV = r.Dev;
}
}
}
public class NoteFraisDescriptor
{
[FactoryOpenAI.ChatJsonFormat(DescriptionF = "Rejeté car n'est pas une pièce justificative ?", DescriptionN = "Afgewezen omdat het geen ondersteunend document is?", DescriptionE = "Rejected because it is not a supporting document?", DescriptionD = "Abgelehnt, da es sich nicht um ein Belegdokument handelt?", IsRequired = true)]
public bool IsRejected { get; set; }
[FactoryOpenAI.ChatJsonFormat(DescriptionF = "Total du ticket", DescriptionN = "Totaal van het ticket", DescriptionE = "Total of the receipt", DescriptionD = "Gesamtbetrag des Kassenbons", IsRequired = true)]
public double Total { get; set; }
[FactoryOpenAI.ChatJsonFormat(DescriptionF = "Devise (code ISO)", DescriptionN = "Munteenheid (ISO-code)", DescriptionE = "Currency (ISO code)", DescriptionD = "Währung (ISO-Code)", IsRequired = true)]
public string Dev { get; set; }
[FactoryOpenAI.ChatJsonFormat(DescriptionF = "Date du ticket (format compatible DateTime? .NET - null, wenn nicht bestimmt)", DescriptionN = "Datum van het ticket (formaat compatibel met DateTime? .NET - null if not determined)", DescriptionE = "Ticket date (format compatible with .NET DateTime? - null indien niet bepaald)", DescriptionD = "Datum des Belegs (Format kompatibel mit .NET DateTime? - null si non déterminé)", IsRequired = true)]
public DateTime? Date { get; set; }
[FactoryOpenAI.ChatJsonFormat(DescriptionF = "Frais de restaurant ?", DescriptionN = "Restaurantkosten?", DescriptionE = "Restaurant expenses?", DescriptionD = "Restaurantkosten?", IsRequired = true)]
public bool IsResto { get; set; }
[FactoryOpenAI.ChatJsonFormat(DescriptionF = "Commentaire éventuel, sinon simplement un espace", DescriptionN = "Eventuele opmerking, anders gewoon een ruimte", DescriptionE = "Optional comment, otherwise simply a space", DescriptionD = "Optionaler Kommentar, ansonsten einfach ein Leerzeichen", IsRequired = true)]
public string Comment { get; set; }
}
}