Projektdateien hinzufügen.
This commit is contained in:
3
EpiAutoReminder.slnx
Normal file
3
EpiAutoReminder.slnx
Normal file
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="EpiAutoReminder/EpiAutoReminder.csproj" />
|
||||
</Solution>
|
||||
1
EpiAutoReminder/.gitignore
vendored
Normal file
1
EpiAutoReminder/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
appsettings.json
|
||||
114
EpiAutoReminder/ConfigLoader.cs
Normal file
114
EpiAutoReminder/ConfigLoader.cs
Normal file
@@ -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<string, string> MailMappings { get; set; } = new Dictionary<string, string>(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<ConfigLoader>(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<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
Logger.Info("Konfiguration erfolgreich geladen.");
|
||||
return config;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Fehler beim Laden der Konfiguration!", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert die Mailadresse zu einem Kürzel, z.B. AR -> axel.reisinger@vt-media.de
|
||||
/// Gibt null zurück, wenn kein Mapping gefunden wurde.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liefert die Mailadresse zu einem Kürzel oder wirft eine Exception, wenn nichts gefunden wurde.
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
33
EpiAutoReminder/EpiAutoReminder.csproj
Normal file
33
EpiAutoReminder/EpiAutoReminder.csproj
Normal file
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="log4net" Version="3.3.0" />
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.103.0" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.83.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="RestSharp" Version="114.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update=".gitignore">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.CHANGEME.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="log4net.config">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
56
EpiAutoReminder/EpiOrders.cs
Normal file
56
EpiAutoReminder/EpiOrders.cs
Normal file
@@ -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<OrderList>(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<ContactDetail>(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<ContactPersonDetail>(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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
26
EpiAutoReminder/Logger.cs.cs
Normal file
26
EpiAutoReminder/Logger.cs.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
1166
EpiAutoReminder/Model/ContactDetail.cs
Normal file
1166
EpiAutoReminder/Model/ContactDetail.cs
Normal file
File diff suppressed because it is too large
Load Diff
239
EpiAutoReminder/Model/ContactPersonDetail.cs
Normal file
239
EpiAutoReminder/Model/ContactPersonDetail.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
// <auto-generated />
|
||||
//
|
||||
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
|
||||
//
|
||||
// using EpiAutoReminder;
|
||||
//
|
||||
// var contactPersonDetail = ContactPersonDetail.FromJson(jsonString);
|
||||
|
||||
namespace EpiAutoReminder
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
public partial class ContactPersonDetail
|
||||
{
|
||||
[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<ContactPersonDetailPayload> Payload { get; set; }
|
||||
|
||||
[JsonProperty("request_duration")]
|
||||
public long RequestDuration { get; set; }
|
||||
|
||||
// shared types from OrderList.cs
|
||||
[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 ContactPersonDetail FromJson(string json)
|
||||
=> JsonConvert.DeserializeObject<ContactPersonDetail>(json, EpiAutoReminder.Converter.Settings);
|
||||
}
|
||||
|
||||
public partial class ContactPersonDetailPayload
|
||||
{
|
||||
[JsonProperty("primary_key")]
|
||||
public long PrimaryKey { get; set; }
|
||||
|
||||
[JsonProperty("contact_pk")]
|
||||
public long ContactPk { get; set; }
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("first_name")]
|
||||
public string FirstName { get; set; }
|
||||
|
||||
[JsonProperty("last_name")]
|
||||
public string LastName { get; set; }
|
||||
|
||||
[JsonProperty("nickname")]
|
||||
public string Nickname { get; set; }
|
||||
|
||||
[JsonProperty("position")]
|
||||
public string Position { get; set; }
|
||||
|
||||
[JsonProperty("date_of_birth")]
|
||||
public string DateOfBirth { get; set; }
|
||||
|
||||
[JsonProperty("department")]
|
||||
public string Department { get; set; }
|
||||
|
||||
[JsonProperty("salutation")]
|
||||
public string Salutation { get; set; }
|
||||
|
||||
[JsonProperty("grade")]
|
||||
public string Grade { get; set; }
|
||||
|
||||
[JsonProperty("has_image")]
|
||||
public bool HasImage { get; set; }
|
||||
|
||||
[JsonProperty("communication")]
|
||||
public List<ContactPersonDetailCommunication> Communication { get; set; }
|
||||
|
||||
[JsonProperty("contact_chrono")]
|
||||
public List<ContactPersonDetailChrono> ContactChrono { get; set; }
|
||||
|
||||
[JsonProperty("searchindex")]
|
||||
public List<object> Searchindex { get; set; }
|
||||
|
||||
[JsonProperty("date_changed")]
|
||||
[JsonConverter(typeof(SafeDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? DateChanged { get; set; }
|
||||
|
||||
[JsonProperty("time_changed")]
|
||||
public long TimeChanged { get; set; }
|
||||
|
||||
[JsonProperty("user_changed")]
|
||||
public string UserChanged { get; set; }
|
||||
|
||||
[JsonProperty("date_created")]
|
||||
[JsonConverter(typeof(SafeDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? DateCreated { get; set; }
|
||||
|
||||
[JsonProperty("time_created")]
|
||||
public long TimeCreated { get; set; }
|
||||
|
||||
[JsonProperty("user_created")]
|
||||
public string UserCreated { get; set; }
|
||||
|
||||
[JsonProperty("_communication_length")]
|
||||
public long CommunicationLength { get; set; }
|
||||
|
||||
[JsonProperty("_contact_chrono_length")]
|
||||
public long ContactChronoLength { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset? CreatedAt => EpiDateHelper.CombineDateAndSeconds(DateCreated, TimeCreated);
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset? ChangedAt => EpiDateHelper.CombineDateAndSeconds(DateChanged, TimeChanged);
|
||||
}
|
||||
|
||||
public partial class ContactPersonDetailCommunication
|
||||
{
|
||||
[JsonProperty("primary_key")]
|
||||
public long PrimaryKey { get; set; }
|
||||
|
||||
[JsonProperty("contact_pk")]
|
||||
public long ContactPk { get; set; }
|
||||
|
||||
[JsonProperty("contact_person_pk")]
|
||||
public long ContactPersonPk { get; set; }
|
||||
|
||||
[JsonProperty("uplink")]
|
||||
public string Uplink { get; set; }
|
||||
|
||||
[JsonProperty("type")]
|
||||
public long Type { get; set; }
|
||||
|
||||
[JsonProperty("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonProperty("phone_match_number")]
|
||||
public string PhoneMatchNumber { get; set; }
|
||||
|
||||
[JsonProperty("position")]
|
||||
public long Position { get; set; }
|
||||
|
||||
[JsonProperty("is_newsletter")]
|
||||
public bool IsNewsletter { get; set; }
|
||||
|
||||
[JsonProperty("is_invoice")]
|
||||
public bool IsInvoice { get; set; }
|
||||
}
|
||||
|
||||
public partial class ContactPersonDetailChrono
|
||||
{
|
||||
[JsonProperty("primary_key")]
|
||||
public long PrimaryKey { get; set; }
|
||||
|
||||
[JsonProperty("contact_pk")]
|
||||
public long ContactPk { get; set; }
|
||||
|
||||
[JsonProperty("contact_person_pk")]
|
||||
public long ContactPersonPk { get; set; }
|
||||
|
||||
[JsonProperty("keyword")]
|
||||
public string Keyword { get; set; }
|
||||
|
||||
[JsonProperty("contact_partner")]
|
||||
public string ContactPartner { get; set; }
|
||||
|
||||
[JsonProperty("date")]
|
||||
[JsonConverter(typeof(SafeDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? Date { get; set; }
|
||||
|
||||
[JsonProperty("time")]
|
||||
public long Time { get; set; }
|
||||
|
||||
[JsonProperty("duration")]
|
||||
public long Duration { get; set; }
|
||||
|
||||
[JsonProperty("com_type")]
|
||||
public long ComType { get; set; }
|
||||
|
||||
[JsonProperty("com_type_str")]
|
||||
public string ComTypeStr { get; set; }
|
||||
|
||||
[JsonProperty("uplink")]
|
||||
public string Uplink { get; set; }
|
||||
|
||||
[JsonProperty("_ref_contact_chrono")]
|
||||
public string RefContactChrono { get; set; }
|
||||
|
||||
[JsonProperty("date_changed")]
|
||||
[JsonConverter(typeof(SafeDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? DateChanged { get; set; }
|
||||
|
||||
[JsonProperty("time_changed")]
|
||||
public long TimeChanged { get; set; }
|
||||
|
||||
[JsonProperty("user_changed")]
|
||||
public string UserChanged { get; set; }
|
||||
|
||||
[JsonProperty("date_created")]
|
||||
[JsonConverter(typeof(SafeDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? DateCreated { get; set; }
|
||||
|
||||
[JsonProperty("time_created")]
|
||||
public long TimeCreated { get; set; }
|
||||
|
||||
[JsonProperty("user_created")]
|
||||
public string UserCreated { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset? CreatedAt => EpiDateHelper.CombineDateAndSeconds(DateCreated, TimeCreated);
|
||||
|
||||
[JsonIgnore]
|
||||
public DateTimeOffset? ChangedAt => EpiDateHelper.CombineDateAndSeconds(DateChanged, TimeChanged);
|
||||
}
|
||||
|
||||
public static class ContactPersonDetailSerialize
|
||||
{
|
||||
public static string ToJson(this ContactPersonDetail self)
|
||||
=> JsonConvert.SerializeObject(self, EpiAutoReminder.Converter.Settings);
|
||||
}
|
||||
}
|
||||
1377
EpiAutoReminder/Model/OrderList.cs
Normal file
1377
EpiAutoReminder/Model/OrderList.cs
Normal file
File diff suppressed because it is too large
Load Diff
177
EpiAutoReminder/O365Connector.cs
Normal file
177
EpiAutoReminder/O365Connector.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using log4net;
|
||||
using Microsoft.Graph;
|
||||
using Microsoft.Graph.Models;
|
||||
using Microsoft.Identity.Client;
|
||||
using Microsoft.Kiota.Abstractions.Authentication;
|
||||
|
||||
namespace EpiAutoReminder
|
||||
{
|
||||
internal class O365Connector
|
||||
{
|
||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(O365Connector));
|
||||
|
||||
private readonly string clientID;
|
||||
private readonly string clientSecret;
|
||||
private readonly string tenantID;
|
||||
private readonly string baseUrl;
|
||||
|
||||
public O365Connector(string clientID, string clientSecret, string tenantID, string baseUrl)
|
||||
{
|
||||
this.clientID = clientID;
|
||||
this.clientSecret = clientSecret;
|
||||
this.tenantID = tenantID;
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public async Task<string> CreateDraftMailAsync(
|
||||
string to,
|
||||
string sender,
|
||||
string subject,
|
||||
string body,
|
||||
string cc = null,
|
||||
string bcc = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Info("CreateDraftMailAsync gestartet.");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(to))
|
||||
throw new ArgumentException("TO darf nicht leer sein.", nameof(to));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(sender))
|
||||
throw new ArgumentException("Sender darf nicht leer sein.", nameof(sender));
|
||||
|
||||
var graphClient = await GetGraphClient();
|
||||
|
||||
var message = new Message
|
||||
{
|
||||
Subject = subject ?? string.Empty,
|
||||
Body = new ItemBody
|
||||
{
|
||||
ContentType = BodyType.Text,
|
||||
Content = body ?? string.Empty
|
||||
},
|
||||
ToRecipients = new List<Recipient>
|
||||
{
|
||||
new Recipient
|
||||
{
|
||||
EmailAddress = new EmailAddress
|
||||
{
|
||||
Address = to.Trim()
|
||||
}
|
||||
}
|
||||
},
|
||||
CcRecipients = string.IsNullOrWhiteSpace(cc)
|
||||
? new List<Recipient>()
|
||||
: new List<Recipient>
|
||||
{
|
||||
new Recipient
|
||||
{
|
||||
EmailAddress = new EmailAddress
|
||||
{
|
||||
Address = cc.Trim()
|
||||
}
|
||||
}
|
||||
},
|
||||
BccRecipients = string.IsNullOrWhiteSpace(bcc)
|
||||
? new List<Recipient>()
|
||||
: new List<Recipient>
|
||||
{
|
||||
new Recipient
|
||||
{
|
||||
EmailAddress = new EmailAddress
|
||||
{
|
||||
Address = bcc.Trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Logger.Info($"Erstelle Draft für Sender '{sender}' an '{to}'.");
|
||||
|
||||
var createdDraft = await graphClient
|
||||
.Users[sender]
|
||||
.Messages
|
||||
.PostAsync(message);
|
||||
|
||||
if (createdDraft == null || string.IsNullOrWhiteSpace(createdDraft.Id))
|
||||
throw new Exception("Entwurf konnte nicht erstellt werden.");
|
||||
|
||||
Logger.Info($"Draft mail created for '{sender}' with id '{createdDraft.Id}'.");
|
||||
|
||||
return createdDraft.Id;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Fehler beim Draft erstellen", ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
private async Task<GraphServiceClient> GetGraphClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Info("Initialisiere GraphServiceClient...");
|
||||
|
||||
var clientApp = ConfidentialClientApplicationBuilder.Create(clientID)
|
||||
.WithClientSecret(clientSecret)
|
||||
.WithAuthority(new Uri($"{baseUrl}/{tenantID}"))
|
||||
.Build();
|
||||
|
||||
var accessTokenProvider = new TokenAcquisitionProvider(clientApp);
|
||||
var authProvider = new BaseBearerTokenAuthenticationProvider(accessTokenProvider);
|
||||
|
||||
Logger.Info("GraphServiceClient erfolgreich initialisiert.");
|
||||
|
||||
return new GraphServiceClient(authProvider);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error initializing GraphServiceClient: " + ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TokenAcquisitionProvider : IAccessTokenProvider
|
||||
{
|
||||
private static readonly ILog Logger = LogManager.GetLogger(typeof(TokenAcquisitionProvider));
|
||||
private readonly IConfidentialClientApplication _clientApp;
|
||||
|
||||
public TokenAcquisitionProvider(IConfidentialClientApplication clientApp)
|
||||
{
|
||||
_clientApp = clientApp;
|
||||
}
|
||||
|
||||
public async Task<string> GetAuthorizationTokenAsync(
|
||||
Uri uri,
|
||||
Dictionary<string, object>? additionalAuthenticationContext = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Info("Fordere Access Token für Microsoft Graph an...");
|
||||
|
||||
var result = await _clientApp
|
||||
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
|
||||
.ExecuteAsync(cancellationToken);
|
||||
|
||||
Logger.Info("Access Token erfolgreich geholt.");
|
||||
|
||||
return result.AccessToken;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error acquiring token: " + ex.Message, ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public AllowedHostsValidator AllowedHostsValidator { get; } = new AllowedHostsValidator();
|
||||
}
|
||||
}
|
||||
183
EpiAutoReminder/Program.cs
Normal file
183
EpiAutoReminder/Program.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using Microsoft.Graph.Models;
|
||||
using Microsoft.Graph.Models.IdentityGovernance;
|
||||
using System.ClientModel.Primitives;
|
||||
using System.Reflection;
|
||||
|
||||
namespace EpiAutoReminder
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
private static ConfigLoader Configuration;
|
||||
private static OrderList OrderList;
|
||||
private static O365Connector O365;
|
||||
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Info("Initializing configuration.");
|
||||
Configuration = ConfigLoader.Instance;
|
||||
Logger.Info("Configuration loaded successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error loading configuration: " + ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("Initializing O365 connector.");
|
||||
O365 = new O365Connector(Configuration.AzureAd.ClientId, Configuration.AzureAd.ClientSecret, Configuration.AzureAd.TenantId, Configuration.Graph.BaseUrl);
|
||||
Logger.Info("O365 connector initialized.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error initializing O365 connector: " + ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("Fetching Epirent Orders.");
|
||||
OrderList = new EpiOrders().getOrderList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error fetching contacts: " + ex.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Prozessroutine
|
||||
|
||||
foreach (Payload Order in OrderList.Payload)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var dispo = Order.OrderSchedule.Find(x => x.Name == "Dispo");
|
||||
var offerdate = Order.OrderDate;
|
||||
|
||||
if (dispo?.DateStart == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (offerdate == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var today = DateTime.Now.Date;
|
||||
var dispoDate = dispo.DateStart.Value.Date;
|
||||
var offerDate = offerdate.Value.Date;
|
||||
|
||||
var daysUntilDispo = (dispoDate - today).Days;
|
||||
var offerAgeDays = (today - offerDate).Days;
|
||||
|
||||
if (daysUntilDispo != Configuration.Epirent.ReminderDaysBeforeDispoStart)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (offerAgeDays < Configuration.Epirent.OfferDateOlderThan)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.Info($"Order found: {Order.Event}. Dispo in {daysUntilDispo} days. Offer is {offerAgeDays} days old.");
|
||||
|
||||
// Jetzt Daten für die Mail zusammensuchen.
|
||||
// Dazu brauchen wir die Ansprechpartnermailadresse, den Bearbeiter inkl. Kontaktdaten
|
||||
// und ein paar Infos über den Job wie Name, ID und evtl. Bestellnummer.
|
||||
|
||||
ContactPersonDetailPayload epiContactPerson = null;
|
||||
ContactDetailPayload epiContactDetail = null;
|
||||
string recipientMailAddress = "";
|
||||
string recipientNameAndSalutation = "";
|
||||
string pronoun = "";
|
||||
|
||||
Logger.Info($"Versuche Kontakte zu finden");
|
||||
// Erst Kontaktperson versuchen
|
||||
if (Order.ContactPerson != null)
|
||||
{
|
||||
Logger.Info($"Aktive Kontaktperson gefunden");
|
||||
epiContactPerson = new EpiOrders()
|
||||
.getContactPersonDetail((int)Order.ContactPerson.PrimaryKey)?
|
||||
.Payload?
|
||||
.FirstOrDefault();
|
||||
Logger.Info($"Kontaktperson: " + epiContactPerson.Name);
|
||||
|
||||
var personMail = epiContactPerson?.Communication?
|
||||
.FirstOrDefault(x => x.Type == 3);
|
||||
|
||||
if (personMail != null && !string.IsNullOrWhiteSpace(personMail.Uplink))
|
||||
{
|
||||
Logger.Info($"Kontaktperson hat Mail: " + personMail.Uplink);
|
||||
|
||||
recipientMailAddress = personMail.Uplink;
|
||||
recipientNameAndSalutation = $"Hallo {epiContactPerson.FirstName} {epiContactPerson.LastName}";
|
||||
pronoun = "dir";
|
||||
}
|
||||
else
|
||||
{
|
||||
epiContactPerson = null; // Fallback auf Kontakt
|
||||
Logger.Info("Kontaktperson hat keine mail - falle zurück auf Kontakt");
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: normalen Kontakt verwenden
|
||||
if (epiContactPerson == null)
|
||||
{
|
||||
Logger.Info($"Nutze Kontaktdatensatz.");
|
||||
|
||||
epiContactDetail = new EpiOrders()
|
||||
.getContact((int)Order.Contact.PrimaryKey)?
|
||||
.Payload?
|
||||
.FirstOrDefault();
|
||||
|
||||
var contactMail = epiContactDetail?.Communication?
|
||||
.FirstOrDefault(x => x.Type == 3);
|
||||
|
||||
if (contactMail == null || string.IsNullOrWhiteSpace(contactMail.Uplink))
|
||||
{
|
||||
// kein Mailkontakt möglich
|
||||
Logger.Info("Keine Mailadresse im Kontakt gefunden - kein Mailkontakt möglich");
|
||||
continue;
|
||||
}
|
||||
|
||||
recipientMailAddress = contactMail.Uplink;
|
||||
recipientNameAndSalutation = $"Liebes Team von {epiContactDetail.Name}";
|
||||
pronoun = "euch";
|
||||
Logger.Info("Mailadressen gefunden.");
|
||||
}
|
||||
var senderMail = ConfigLoader.Instance.GetMailAddressByShortcut(Order.EditorShort);
|
||||
|
||||
Logger.Info("Versuche Mail an " + recipientNameAndSalutation + " (" + recipientMailAddress + ") von " + senderMail + " zu senden");
|
||||
|
||||
var bodyText =
|
||||
recipientNameAndSalutation + "," + Environment.NewLine + Environment.NewLine +
|
||||
$"ich hoffe, es geht {pronoun} gut." + Environment.NewLine +
|
||||
$"vor einiger Zeit habe ich {pronoun} unser Angebot \"{Order.Event}\" (Angebotsnummer: {Order.OrderNoFmt}) zukommen lassen. \r\nDa ich bisher noch nichts gehört habe, wollte ich kurz nachfragen, ob das Angebot noch aktuell ist oder ob ich bei der weiteren Planung unterstützen kann." + Environment.NewLine + Environment.NewLine +
|
||||
"Gerne stehe ich für Rückfragen, Anpassungen oder weitere Abstimmungen jederzeit zur Verfügung." + Environment.NewLine + Environment.NewLine +
|
||||
"Ich freue mich auf eine Rückmeldung.";
|
||||
|
||||
var draftId = O365.CreateDraftMailAsync(
|
||||
recipientMailAddress,
|
||||
senderMail,
|
||||
|
||||
"Erinnerung zum offenen Angebot " + Order.OrderNoFmt + " " + Order.Event,
|
||||
bodyText, null, senderMail).GetAwaiter().GetResult();
|
||||
|
||||
Logger.Info("Draft erstellt mit ID: " + draftId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error processing order " + Order.Event + ": " + ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
EpiAutoReminder/appsettings.CHANGEME.json
Normal file
24
EpiAutoReminder/appsettings.CHANGEME.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"AzureAd": {
|
||||
"ClientId": "xxx",
|
||||
"TenantId": "xxx",
|
||||
"ClientSecret": "xxx"
|
||||
},
|
||||
"Graph": {
|
||||
"BaseUrl": "https://login.microsoftonline.com"
|
||||
},
|
||||
"Epirent": {
|
||||
"Server": "xxx",
|
||||
"Port": 8080,
|
||||
"Token": "xxx",
|
||||
"Mandant": 0,
|
||||
"ReminderDaysBeforeDispoStart": 21,
|
||||
"OfferDateOlderThan": 14
|
||||
},
|
||||
"MailMappings": {
|
||||
"TU": "test.user@vt-media.de",
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
24
EpiAutoReminder/log4net.config
Normal file
24
EpiAutoReminder/log4net.config
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<log4net>
|
||||
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
|
||||
<file value="logs/app.log" />
|
||||
<appendToFile value="true" />
|
||||
<rollingStyle value="Date" />
|
||||
<datePattern value="yyyy-MM-dd'.log'" />
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="[%date] [%level] %logger - %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
|
||||
<layout type="log4net.Layout.PatternLayout">
|
||||
<conversionPattern value="[%date] [%level] %logger - %message%newline" />
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root>
|
||||
<level value="ALL" />
|
||||
<appender-ref ref="RollingFileAppender" />
|
||||
<appender-ref ref="ConsoleAppender" />
|
||||
</root>
|
||||
</log4net>
|
||||
Reference in New Issue
Block a user