diff --git a/EpiAutoReminder.slnx b/EpiAutoReminder.slnx
new file mode 100644
index 0000000..13cad60
--- /dev/null
+++ b/EpiAutoReminder.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/EpiAutoReminder/.gitignore b/EpiAutoReminder/.gitignore
new file mode 100644
index 0000000..d7be6ac
--- /dev/null
+++ b/EpiAutoReminder/.gitignore
@@ -0,0 +1 @@
+appsettings.json
\ No newline at end of file
diff --git a/EpiAutoReminder/ConfigLoader.cs b/EpiAutoReminder/ConfigLoader.cs
new file mode 100644
index 0000000..c225747
--- /dev/null
+++ b/EpiAutoReminder/ConfigLoader.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+
+namespace EpiAutoReminder
+{
+ public class ConfigLoader
+ {
+ public AzureAdConfig AzureAd { get; set; }
+ public GraphConfig Graph { get; set; }
+ public EpirentConfig Epirent { get; set; }
+
+ // Neues Mapping
+ public Dictionary MailMappings { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ private static readonly string configFilePath = "appsettings.json";
+ private static ConfigLoader _instance;
+
+ public static ConfigLoader Instance => _instance ??= LoadConfig();
+
+ public ConfigLoader() { }
+
+ public static ConfigLoader LoadConfig()
+ {
+ Logger.Info("Lade Konfigurationsdatei...");
+
+ try
+ {
+ if (!File.Exists(configFilePath))
+ {
+ Logger.Error($"Konfigurationsdatei '{configFilePath}' wurde nicht gefunden!");
+ throw new FileNotFoundException($"Config-Datei '{configFilePath}' wurde nicht gefunden!");
+ }
+
+ string json = File.ReadAllText(configFilePath);
+
+ var options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
+ var config = JsonSerializer.Deserialize(json, options);
+
+ if (config == null)
+ {
+ Logger.Error("Fehler: Konfiguration konnte nicht deserialisiert werden.");
+ throw new Exception("Konfiguration konnte nicht geladen werden.");
+ }
+
+ config.MailMappings ??= new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ Logger.Info("Konfiguration erfolgreich geladen.");
+ return config;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("Fehler beim Laden der Konfiguration!", ex);
+ throw;
+ }
+ }
+
+ ///
+ /// Liefert die Mailadresse zu einem Kürzel, z.B. AR -> axel.reisinger@vt-media.de
+ /// Gibt null zurück, wenn kein Mapping gefunden wurde.
+ ///
+ public string GetMailAddressByShortcut(string shortcut)
+ {
+ if (string.IsNullOrWhiteSpace(shortcut))
+ return null;
+
+ if (MailMappings != null && MailMappings.TryGetValue(shortcut.Trim(), out var mailAddress))
+ return mailAddress;
+
+ return null;
+ }
+
+ ///
+ /// Liefert die Mailadresse zu einem Kürzel oder wirft eine Exception, wenn nichts gefunden wurde.
+ ///
+ public string GetRequiredMailAddressByShortcut(string shortcut)
+ {
+ var mailAddress = GetMailAddressByShortcut(shortcut);
+
+ if (string.IsNullOrWhiteSpace(mailAddress))
+ throw new KeyNotFoundException($"Für das Kürzel '{shortcut}' wurde keine Mailadresse gefunden.");
+
+ return mailAddress;
+ }
+ }
+
+ public class AzureAdConfig
+ {
+ public string ClientId { get; set; }
+ public string TenantId { get; set; }
+ public string ClientSecret { get; set; }
+ }
+
+ public class GraphConfig
+ {
+ public string BaseUrl { get; set; }
+ }
+
+ public class EpirentConfig
+ {
+ public string Server { get; set; }
+ public int Port { get; set; }
+ public string Token { get; set; }
+ public int Mandant { get; set; }
+ public int ReminderDaysBeforeDispoStart { get; set; }
+ public int OfferDateOlderThan { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EpiAutoReminder/EpiAutoReminder.csproj b/EpiAutoReminder/EpiAutoReminder.csproj
new file mode 100644
index 0000000..7656eb3
--- /dev/null
+++ b/EpiAutoReminder/EpiAutoReminder.csproj
@@ -0,0 +1,33 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/EpiAutoReminder/EpiOrders.cs b/EpiAutoReminder/EpiOrders.cs
new file mode 100644
index 0000000..cb9a7b6
--- /dev/null
+++ b/EpiAutoReminder/EpiOrders.cs
@@ -0,0 +1,56 @@
+using Newtonsoft.Json;
+
+using RestSharp;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace EpiAutoReminder
+{
+ internal class EpiOrders
+ {
+ ConfigLoader Configuration = ConfigLoader.Instance;
+ RestClient epirentserver;
+
+
+ public EpiOrders()
+ {
+ epirentserver = new RestClient("http://" + Configuration.Epirent.Server + ":" + Configuration.Epirent.Port);
+
+ }
+ public OrderList getOrderList()
+ {
+ OrderList orderlist = JsonConvert.DeserializeObject(getOrderJson());
+ return orderlist;
+ }
+
+ public ContactDetail getContact(int ContactPrimaryKey)
+ {
+ RestRequest request = new RestRequest("/v1/contact/" + ContactPrimaryKey + "?cl=" + Configuration.Epirent.Mandant, RestSharp.Method.Get);
+ request.AddHeader("X-EPI-NO-SESSION", "True");
+ request.AddHeader("X-EPI-ACC-TOK", Configuration.Epirent.Token);
+ request.RequestFormat = DataFormat.Json;
+ return JsonConvert.DeserializeObject(epirentserver.ExecuteGet(request).Content);
+ }
+ public ContactPersonDetail getContactPersonDetail(int ContactPersonPrimaryKey)
+ {
+ RestRequest request = new RestRequest("/v1/cperson/" + ContactPersonPrimaryKey + "?cl=" + Configuration.Epirent.Mandant, RestSharp.Method.Get);
+ request.AddHeader("X-EPI-NO-SESSION", "True");
+ request.AddHeader("X-EPI-ACC-TOK", Configuration.Epirent.Token);
+ request.RequestFormat = DataFormat.Json;
+ return JsonConvert.DeserializeObject(epirentserver.ExecuteGet(request).Content);
+ }
+
+
+ private String getOrderJson()
+ {
+ RestRequest request = new RestRequest("/v1/order/filter?cl="+ Configuration.Epirent.Mandant + "&ir=true&ia=false&icl=false&ico=false&icv=true", RestSharp.Method.Get);
+ request.AddHeader("X-EPI-NO-SESSION", "True");
+ request.AddHeader("X-EPI-ACC-TOK", Configuration.Epirent.Token);
+ request.RequestFormat = DataFormat.Json;
+ return epirentserver.ExecuteGet(request).Content;
+
+ }
+ }
+}
diff --git a/EpiAutoReminder/Logger.cs.cs b/EpiAutoReminder/Logger.cs.cs
new file mode 100644
index 0000000..9b77c42
--- /dev/null
+++ b/EpiAutoReminder/Logger.cs.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+using log4net.Config;
+using log4net;
+
+namespace EpiAutoReminder
+{
+
+ public static class Logger
+ {
+ private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
+
+ static Logger()
+ {
+ var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
+ XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));
+ }
+
+ public static void Info(string message) => log.Info(message);
+ public static void Warn(string message) => log.Warn(message);
+ public static void Error(string message, Exception ex = null) => log.Error(message, ex);
+ public static void Debug(string message) => log.Debug(message);
+ }
+ }
diff --git a/EpiAutoReminder/Model/ContactDetail.cs b/EpiAutoReminder/Model/ContactDetail.cs
new file mode 100644
index 0000000..718e343
--- /dev/null
+++ b/EpiAutoReminder/Model/ContactDetail.cs
@@ -0,0 +1,1166 @@
+//
+//
+// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
+//
+// using EpiAutoReminder;
+//
+// var contactDetail = ContactDetail.FromJson(jsonString);
+
+namespace EpiAutoReminder
+{
+ using System;
+ using System.Collections.Generic;
+ using Newtonsoft.Json;
+
+ public partial class ContactDetail
+ {
+ [JsonProperty("success")]
+ public bool Success { get; set; }
+
+ [JsonProperty("namespace")]
+ public string Namespace { get; set; }
+
+ [JsonProperty("action")]
+ public string Action { get; set; }
+
+ [JsonProperty("templateSignature")]
+ public string TemplateSignature { get; set; }
+
+ [JsonProperty("payload_length")]
+ public long PayloadLength { get; set; }
+
+ [JsonProperty("is_send_payload")]
+ public bool IsSendPayload { get; set; }
+
+ [JsonProperty("payload")]
+ public List Payload { get; set; }
+
+ // Diese Typen kommen bereits aus OrderList.cs
+ //[JsonProperty("filter_options")]
+ //public FilterOptions FilterOptions { get; set; }
+
+ //[JsonProperty("request_duration")]
+ //public long RequestDuration { get; set; }
+
+ //[JsonProperty("req_datetime")]
+ //public ReqDatetime ReqDatetime { get; set; }
+
+ //[JsonProperty("_dbg_request_data")]
+ //public DbgRequestData DbgRequestData { get; set; }
+
+ //[JsonProperty("_dbg_user_info")]
+ //public UserInfo DbgUserInfo { get; set; }
+
+ public static ContactDetail FromJson(string json)
+ => JsonConvert.DeserializeObject(json, EpiAutoReminder.Converter.Settings);
+ }
+
+ public partial class ContactDetailPayload
+ {
+ [JsonProperty("primary_key")]
+ public long PrimaryKey { get; set; }
+
+ [JsonProperty("customer_no")]
+ public long CustomerNo { get; set; }
+
+ [JsonProperty("supplier_no")]
+ public long SupplierNo { get; set; }
+
+ [JsonProperty("is_single_person")]
+ public bool IsSinglePerson { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("name1")]
+ public string Name1 { get; set; }
+
+ [JsonProperty("name2")]
+ public string Name2 { get; set; }
+
+ [JsonProperty("name3")]
+ public string Name3 { get; set; }
+
+ [JsonProperty("matchcode")]
+ public string Matchcode { get; set; }
+
+ [JsonProperty("is_interested")]
+ public bool IsInterested { get; set; }
+
+ [JsonProperty("is_customer")]
+ public bool IsCustomer { get; set; }
+
+ [JsonProperty("is_supplier")]
+ public bool IsSupplier { get; set; }
+
+ [JsonProperty("is_location")]
+ public bool IsLocation { get; set; }
+
+ [JsonProperty("is_vip")]
+ public bool IsVip { get; set; }
+
+ [JsonProperty("is_colleague")]
+ public bool IsColleague { get; set; }
+
+ [JsonProperty("salutation")]
+ public string Salutation { get; set; }
+
+ [JsonProperty("grade")]
+ public string Grade { get; set; }
+
+ [JsonProperty("notes_positive")]
+ public string NotesPositive { get; set; }
+
+ [JsonProperty("notes_negative")]
+ public string NotesNegative { get; set; }
+
+ [JsonProperty("date_of_birth")]
+ public string DateOfBirth { get; set; }
+
+ [JsonProperty("date_changed")]
+ [JsonConverter(typeof(SafeDateTimeOffsetConverter))]
+ public DateTimeOffset? DateChanged { get; set; }
+
+ [JsonProperty("time_changed")]
+ public long TimeChanged { get; set; }
+
+ [JsonProperty("date_created")]
+ [JsonConverter(typeof(SafeDateTimeOffsetConverter))]
+ public DateTimeOffset? DateCreated { get; set; }
+
+ [JsonProperty("time_created")]
+ public long TimeCreated { get; set; }
+
+ [JsonProperty("is_active")]
+ public bool IsActive { get; set; }
+
+ [JsonProperty("terms_of_payment_pk")]
+ public long TermsOfPaymentPk { get; set; }
+
+ [JsonProperty("is_sysstd_terms_of_payment")]
+ public bool IsSysstdTermsOfPayment { get; set; }
+
+ [JsonProperty("vat_id")]
+ public string VatId { get; set; }
+
+ [JsonProperty("eori_no")]
+ public string EoriNo { get; set; }
+
+ [JsonProperty("tax_no")]
+ public string TaxNo { get; set; }
+
+ [JsonProperty("tax_office")]
+ public string TaxOffice { get; set; }
+
+ [JsonProperty("commercial_register_no")]
+ public string CommercialRegisterNo { get; set; }
+
+ [JsonProperty("commercial_register_office")]
+ public string CommercialRegisterOffice { get; set; }
+
+ [JsonProperty("is_blocked_order")]
+ public bool IsBlockedOrder { get; set; }
+
+ [JsonProperty("is_separate_invoice_address")]
+ public bool IsSeparateInvoiceAddress { get; set; }
+
+ [JsonProperty("invoice_address")]
+ public InvoiceAddress InvoiceAddress { get; set; }
+
+ [JsonProperty("has_image")]
+ public bool HasImage { get; set; }
+
+ [JsonProperty("address")]
+ public ContactDetailAddress Address { get; set; }
+
+ [JsonProperty("address_list")]
+ public List AddressList { get; set; }
+
+ [JsonProperty("address_delivery")]
+ public object AddressDelivery { get; set; }
+
+ [JsonProperty("communication")]
+ public List Communication { get; set; }
+
+ [JsonProperty("_communication_length")]
+ public long CommunicationLength { get; set; }
+
+ [JsonProperty("contact_person")]
+ public List ContactPerson { get; set; }
+
+ [JsonProperty("_contact_person_length")]
+ public long ContactPersonLength { get; set; }
+
+ [JsonProperty("contact_chrono")]
+ public List ContactChrono { get; set; }
+
+ [JsonProperty("_contact_chrono_length")]
+ public long ContactChronoLength { get; set; }
+
+ [JsonProperty("order")]
+ public List Order { get; set; }
+
+ [JsonProperty("_order_length")]
+ public long OrderLength { get; set; }
+
+ [JsonProperty("banking_customer")]
+ public BankingCustomer BankingCustomer { get; set; }
+
+ [JsonProperty("banking_supplier")]
+ public BankingSupplier BankingSupplier { get; set; }
+
+ [JsonProperty("price_range")]
+ public PriceRange PriceRange { get; set; }
+
+ [JsonProperty("searchindex")]
+ public List