refactor: migrate project structure by reorganizing realization code snippets into documentation and analysis categories.

This commit is contained in:
2026-05-27 10:48:45 +02:00
parent eb82e4e0b2
commit 24c0593f15
116 changed files with 3309 additions and 236 deletions

BIN
02_Analyse_Konzept/HostBooking/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<configuration>
<appSettings>
<!-- BEGINN Prozesseinstellungen -->
<add key="Conn1" value="HostBooking"/>
<!-- Telegrammverbuchung aufteilen nach der letzten Stelle der HU Nummer?-->
<add key="UseLoadBalancing" value="false"/>
<!--Polling Intervall der Producerklasse-->
<add key="Intervall" value="201"/>
<!-- so viele Telegramme bekommt ein Consumer maximal auf einen Schlag-->
<add key="ConsumerQueueLength" value="200"/>
<!-- Überprüfungsintervall -->
<add key="ctrlTimer" value="12000" />
<!-- ENDE Prozesseinstellungen-->
<!-- BEGINN Log -->
<!-- 0=ERROR 3=INFO 5=DEBUG 7=LOWLEVEL -->
<add key="LogLevel" value="7"/>
<!-- Aufteilung der Log-Files per Thread -->
<add key="SplitLogFilesByThreadName" value="true"/>
<add key="MainThreadNames" value="-"/>
<!-- ENDE Log -->
<!-- Datenbankverbindung -->
<add key="Eigentuemer" value="Wcs" />
</appSettings>
</configuration>

View File

@@ -0,0 +1,45 @@
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms;
namespace Gebhardt.StoreWare.Wcs.HostBooking
{
public class LeNoMissingException : FromWmsException
{
public LeNoMissingException(string message) : base(message) { }
}
public class LeNoWrongFormatException : FromWmsException
{
public LeNoWrongFormatException(string message) : base(message) { }
}
public class LeNoTooShortException : FromWmsException
{
public LeNoTooShortException(string message) : base(message) { }
}
public class LeNoTooLongException : FromWmsException
{
public LeNoTooLongException(string message) : base(message) { }
}
public class TransportDestinationInvalidException : FromWmsException
{
public TransportDestinationInvalidException(string message) : base(message) { }
}
public class LeAlreadyInStorageException : FromWmsException
{
public LeAlreadyInStorageException(string message) : base(message) { }
}
public class LeNotReachableException : FromWmsException
{
public LeNotReachableException(string message) : base(message) { }
}
public class LeHasActiveTransportOrderException : FromWmsException
{
public LeHasActiveTransportOrderException(string message) : base(message) { }
}
}

View File

@@ -0,0 +1,132 @@
using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common.Unity;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms;
using System;
using System.Collections.Generic;
using System.Configuration;
using Unity;
namespace Gebhardt.StoreWare.Wcs.HostBooking
{
internal class Haupt
{
[STAThread]
private static void Main()
{
AppDomain.CurrentDomain.UnhandledException += HandleAppDomainException;
try
{
IUnityContainer container = WcsContainerFactory.GetChildInstance();
container.RegisterFromWmsHandlers();
container.RegisterFromWmsServices();
if (AppConfigVerifier.CheckAndWriteToLog())
{
// LifeTimer Intervall ist in app.config ctrlTimer eingestellt
int lifeTimerInterval = Convert.ToInt32(ConfigurationManager.AppSettings["ctrlTimer"]);
string[] usedConnections = GetUsedConnections();
ProcessManager manager = new(lifeTimerInterval, true,
ConfigurationManager.AppSettings["ProcessClass"] == "None" ? ProcessClass.None : ProcessClass.Application,
usedConnections);
//Worker (Producer und Consumer) erstellen und verknüpfen
RegisterWorkers(manager, lifeTimerInterval, container);
// Starten
manager.RunWorkers();
}
}
catch (Exception exception)
{
Log.WriteException(exception);
Console.WriteLine("main thread: e: {0} e: {1}", exception.StackTrace, exception);
}
}
private static void HandleAppDomainException(object sender, UnhandledExceptionEventArgs e)
{
Exception exception = (Exception)e.ExceptionObject;
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
Log.WriteException(exception);
Console.WriteLine($@"main thread: e: {exception.StackTrace} e: {exception}");
}
/// <summary>
/// Ruft die Conn Einträge aus der App.config ab die nicht leer sind
/// </summary>
/// <returns></returns>
private static string[] GetUsedConnections()
{
ProcessParameter parameter = new();
var connections = new List<string> { parameter.Conn1, parameter.Conn2, parameter.Conn3, parameter.Conn4, parameter.Conn5, parameter.Conn6 };
string[] usedConnections = connections.FindAll(x => x != "leer").ToArray();
return usedConnections;
}
/// <summary>
/// Erstellt einen Producer und je nach useLoadBalancing einen oder elf Consumer und weißt diese dem Producer und dem
/// Manager zu.
/// </summary>
/// <param name="manager"></param>
/// <param name="ctrTimerInterval">ctrTimer aus App.config</param>
internal static void RegisterWorkers(ProcessManager manager, int ctrTimerInterval, IUnityContainer unityContainer)
{
//AppSettings auslesen
bool useLoadBalancing;
try
{
useLoadBalancing = Convert.ToBoolean(ConfigurationManager.AppSettings["UseLoadBalancing"]);
}
catch (Exception e)
{
useLoadBalancing = false;
Log.Write(LogLevel.Error, "Parameter 'useLoadBalancing' fehlt in der App.config, setze auf false");
}
int consumerQueueLength;
try
{
consumerQueueLength = Convert.ToInt32(ConfigurationManager.AppSettings["ConsumerQueueLength"]);
}
catch (Exception e)
{
consumerQueueLength = 200;
Log.Write(LogLevel.Error, "Parameter 'consumerQueueLength' fehlt in der App.config, setze auf 200");
}
// Producer anmelden
int pollingInterval = Convert.ToInt32(ConfigurationManager.AppSettings["Intervall"]);
HostBookingProducer producer = new(pollingInterval, useLoadBalancing, consumerQueueLength);
manager.RegisterWorker(producer);
string consumerName;
HostBookingConsumer consumer;
//Wenn useLoadBalancing = true, dann wird für jede Endziffer der HU ein Consumer erstellt und einer für Telegramme ohne HU (Default)
if (useLoadBalancing)
{
for (int i = 0; i < 10; i++)
{
consumerName = $"Consumer_{i}";
consumer = new HostBookingConsumer(consumerName, ctrTimerInterval / 2, consumerQueueLength, unityContainer);
producer.AddConsumer(consumerName, consumer);
manager.RegisterWorker(consumer);
}
consumerName = "Consumer_Default";
consumer = new HostBookingConsumer(consumerName, ctrTimerInterval / 2, consumerQueueLength, unityContainer);
producer.AddConsumer(consumerName, consumer);
manager.RegisterWorker(consumer);
}
//ohne useLoadBalancing gibt es nur einen Consumer der alle Telegramme verarbeitet
else
{
consumerName = "Consumer_All";
consumer = new HostBookingConsumer(consumerName, ctrTimerInterval / 2, consumerQueueLength, unityContainer);
producer.AddConsumer(consumerName, consumer);
manager.RegisterWorker(consumer);
}
}
}
}

View File

@@ -0,0 +1,44 @@
using Gebhardt.Shared;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using System;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Unity;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms;
namespace Gebhardt.StoreWare.Wcs.HostBooking
{
public class HostBookingConsumer : Consumer<IHostMessage>
{
private readonly IUnityContainer _unityContainer;
public HostBookingConsumer(string name, int aliveTime, int queueLength, IUnityContainer unityContainer)
: base(name, aliveTime, true, queueLength)
{
_unityContainer = unityContainer;
}
public override bool DoWork(IHostMessage hostMessage)
{
IHostMessageFromWmsService service = _unityContainer.Resolve<IHostMessageFromWmsService>();
try
{
dynamic handler = _unityContainer.Resolve(typeof(IHandleRecord<>).MakeGenericType(hostMessage.GetType()), hostMessage.RecordType);
Log.Write(LogLevel.Info, $"process message [{hostMessage}]");
handler.Handle((dynamic)hostMessage);
hostMessage.SetFinished();
}
catch (Exception e)
{
if (e.GetType() != typeof(FromWmsException))
{
Log.WriteException(e);
}
hostMessage.SetFailed(e.Message);
}
service.Update(hostMessage);
return true;
}
}
}

View File

@@ -0,0 +1,203 @@
using Gebhardt.Shared;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.Constants;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using static Gebhardt.StoreWare.Wcs.Common.ConstantsCommon.WatchdogConstants;
namespace Gebhardt.StoreWare.Wcs.HostBooking
{
internal class HostBookingProducer : Producer<IHostMessage>
{
private bool _firstExecution = true;
private int _consumerQueueLength;
private bool _useLoadBalancing;
private readonly IHostMessageFromWmsService _service = new HostMessageFromWmsService();
/// <summary>
/// Fügt im Dictionary dem angegebenen Consumer das Messagem hinzu
/// </summary>
/// <param name="dataForConsumers"></param>
/// <param name="consumer"></param>
/// <param name="fromWms"></param>
private void AddDataForConsumer(ref Dictionary<string, List<IHostMessage>> dataForConsumers, string consumer, IHostMessage fromWms)
{
if (dataForConsumers.ContainsKey(consumer))
{
dataForConsumers[consumer].Add(fromWms);
}
else
{
dataForConsumers.Add(consumer, new List<IHostMessage> { fromWms });
}
}
/// <summary>
/// Bestimmt den Consumer, der für den Datensatz verantwortlich ist
/// </summary>
/// <param name="consumersWithDemand"></param>
/// <param name="criterion"></param>
/// <returns>Consumer Name, wenn ein passender Consumer in der lsite ist, null, wenn kein passender Consumer in der Liste ist</returns>
private string GetConsumerForBooking(List<string> consumersWithDemand, string criterion)
{
//Gibt es eine Letzte Stelle, sonst default
if (!criterion.IsNullOrEmptyOrDbEmpty())
{
//Wir entscheiden mit der letzten Stelle der LE, welcher LE-Consumer verbucht oder ob der default Consumer das tun muss
string endOfLe = criterion.Substring(criterion.Length - 1);
//Der Name des Consumer endet mit dem gleichen Zeichen
if (consumersWithDemand.Any(c => c.EndsWith(endOfLe)))
{
return consumersWithDemand.First(c => c.EndsWith(endOfLe));
}
//Soll dieses Messagem von einem speziellen Consumer bearbeitet werden? Aber dieser ist beschäftigt
else if (Regex.IsMatch(endOfLe, "[0-9]"))
{
//Messagem auslassen - keinem Consumer zuordnen
return null;
}
//HU endet mit einem Zeichen, dass zu keinem Consumer passt also Default oder auslassen
else
{
//Default Consumer muss mit Default enden!
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
}
}
else
{
//Default Consumer muss mit Default enden!
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
}
}
protected override Dictionary<string, List<IHostMessage>> GetDataForConsumers(List<string> consumersWithDemand)
{
//TODO: jub evtl. die Messagem Stau Meldung aus KW übernehmen
Dictionary<string, List<IHostMessage>> dataForConsumers = new Dictionary<string, List<IHostMessage>>();
try
{
//using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
if (consumersWithDemand.Count > 0)
{
List<IHostMessage> fromWmsEntries;
if (_firstExecution)
{
//beim ersten mal nach Neustart werden die InProgress Messages nochmal auf Pending zurückgesetzt
//Ref == null damit nur Kopfnachrichten gefunden werden
fromWmsEntries = _service.GetAllEntries(t => t.Status == Status.InProgress).ToList();
using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
fromWmsEntries.ForEach(message =>
{
FromWms fromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
db.Attach(fromWms);
fromWms.Status = Status.Pending;
});
db.SaveChanges();
Log.Write(LogLevel.Info, $"Erste Produce Schleife nach Neustart, setze {fromWmsEntries.Count} Telegramme zur Sicherheit nochmals von InProgress auf Pending");
_firstExecution = false;
}
//nur Messages, die noch an keinen Consumer gegeben wurden
//es werden maximal so viele Messages abgerufen, wie ein einzelner Consumer annehmen könnte, damit das TryAdd sicher klappt und der Status nicht fälschlich
//gesetzt wird
//Ref == null damit nur Kopfnachrichten gefunden werden
fromWmsEntries = _service.GetAllEntries(a => a.Status == Status.Pending).OrderBy(a => a.Id).Take(_consumerQueueLength).ToList();// db.FromWms.Where(a => a.Ref == null).Where(a => a.Status == Status.Pending).OrderBy(a => a.Id).Take(_consumerQueueLength).ToList();
if (fromWmsEntries.Any())
{
using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
//ohne LoadBalancing erhält der erste Consumer alle Messages zum Verbuchen
if (!_useLoadBalancing)
{
//der Consumer muss also alle Messages verarbeiten und nicht nur loggen
dataForConsumers.Add(consumersWithDemand.FirstOrDefault() ?? string.Empty, fromWmsEntries);
//Status auf InProgress damit jede Message nur einmal abgerufen wird
fromWmsEntries.ForEach(message => {
FromWms fromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
db.Attach(fromWms);
fromWms.Status = Status.InProgress;
});
Log.Write(LogLevel.Low, $"Kein LoadBalancing aktiv, {fromWmsEntries.Count} neue HostMessages für Consumer {consumersWithDemand.FirstOrDefault()} gefunden");
}
//mit LoadBalancing wird nach der letzen Ziffer der ersten HU der Message auf 10 Consumer verteilt,
//enthält die Message keine HU wird es an Consumer 11 übergeben.
else
{
Log.Write(LogLevel.Low, $"LoadBalancing aktiv, {fromWmsEntries.Count} neue HostMessages gefunden, verteile auf Consumer");
foreach (IHostMessage message in fromWmsEntries)
{
try
{
FromWms fromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
db.Attach(fromWms);
Log.Write(LogLevel.Low, $"Producerschleife für {fromWms}");
string consumer;
//keine HU in der Message, dann dem Default Consumer zuordnen
if (fromWms.LeNo.IsNullOrEmptyOrDbEmpty())
{
consumer = GetConsumerForBooking(consumersWithDemand, null);
//wenn der Default Consumer nicht in der Liste war, Message auslassen
if (consumer != null)
{
AddDataForConsumer(ref dataForConsumers, consumer, message);
//Status auf InProgress damit die Message nur einmal abgerufen wird
fromWms.Status = Status.InProgress;
}
}
//Messages mit HU
else
{
consumer = GetConsumerForBooking(consumersWithDemand, fromWms.LeNo);
AddDataForConsumer(ref dataForConsumers, consumer, message);
//Status auf InProgress damit die Message nur einmal abgerufen wird
fromWms.Status = Status.InProgress;
}
}
catch (Exception ex)
{
Log.WriteException(ex);
}
}
Log.Write(LogLevel.Low, $"Producerschleife beendet");
}
//Status Updates für alle weitergereichten Messages
db.SaveChanges();
}
else
{
Log.Write(LogLevel.Debug, 60, "Keine Messages in FromWms");
}
}
else
{
Log.Write(LogLevel.Debug, 60, "Kein Consumer hat demand");
}
return dataForConsumers;
}
catch (Exception e)
{
Log.WriteException(e);
return dataForConsumers;
}
}
public HostBookingProducer(int workinterval, bool useLoadBalancing, int consumerQueueLength) : base(typeof(HostBookingProducer).Name, workinterval, true)
{
_consumerQueueLength = consumerQueueLength;
_useLoadBalancing = useLoadBalancing;
}
}
}

View File

@@ -0,0 +1,90 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using Unity;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared.Process;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Mapping;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Helpers;
using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
internal class FromWmsBookingConsumer : Consumer<IHostMessage>
{
#region Private Fields
private readonly IUnityContainer _unityContainer;
private readonly IHostMessageFromWmsService _serviceFromWms;
#endregion Private Fields
#region Public Constructors
public FromWmsBookingConsumer(string name, int aliveTime, int queueLength, IUnityContainer unityContainer)
: base(name, aliveTime, true, queueLength)
{
_unityContainer = unityContainer;
_serviceFromWms = _unityContainer.Resolve<IHostMessageFromWmsService>();
}
#endregion Public Constructors
#region Public Methods
public override bool DoWork(IHostMessage hostMessage)
{
try
{
IHandleRecord<IHostMessage> handler = null;
try
{
//handler = _unityContainer.Resolve<IHandleRecord>(hostMessage.RecordType);
handler = (IHandleRecord<IHostMessage>)_unityContainer.Resolve(hostMessage.GetType()); //TODO test
}
catch (Exception e)
{
Log.Write(LogLevel.Error, $"IHandleRecord on UnityContainer in RegisterFromWmsServices from MessageInitializer.cs not registired. '{e.Message}'");
}
if (handler == null)
{
hostMessage.Handle();
}
else
{
handler.Handle(hostMessage);
hostMessage.SetFinished();
}
}
catch (Exception e)
{
//Log.WriteException(e);
string exceptionMessage = e.InnerException?.InnerException?.Message ?? e.InnerException?.Message ?? e.Message;
Log.Write(LogLevel.Error, $"Nachricht {hostMessage.RecordType} kann nicht verbucht werden: {exceptionMessage}");
hostMessage.SetFailed(e.Message);
}
finally
{
_serviceFromWms.Update(hostMessage);
}
return true;
}
#endregion Public Methods
}
}

View File

@@ -0,0 +1,224 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using System.Text.RegularExpressions;
using Unity;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared.Process;
using Gebhardt.Shared.Process.ProducerConsumer;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Mapping;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Helpers;
using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
internal class FromWmsBookingProducer : Producer<IHostMessage>
{
#region Private Fields
private bool _firstExecution = true;
private int _consumerQueueLength;
private bool _useLoadBalancing;
private readonly IUnityContainer _unityContainer;
private readonly IHostMessageFromWmsService _serviceFromWms;
#endregion Private Fields
#region Private Methods
/// <summary>
/// Fügt im Dictionary dem angegebenen Consumer das Messagem hinzu
/// </summary>
/// <param name="dataForConsumers"></param>
/// <param name="consumer"></param>
/// <param name="hostMessage">FromWms - IHostMessage</param>
private void AddDataForConsumer(ref Dictionary<string, List<IHostMessage>> dataForConsumers, string consumer, IHostMessage hostMessage)
{
if (dataForConsumers.ContainsKey(consumer))
{
dataForConsumers[consumer].Add(hostMessage);
}
else
{
dataForConsumers.Add(consumer, new List<IHostMessage> { hostMessage });
}
}
/// <summary>
/// Bestimmt den Consumer, der für den Datensatz verantwortlich ist
/// </summary>
/// <param name="consumersWithDemand"></param>
/// <param name="criterion"></param>
/// <returns>Consumer Name, wenn ein passender Consumer in der lsite ist, null, wenn kein passender Consumer in der Liste ist</returns>
private string GetConsumerForBooking(List<string> consumersWithDemand, string criterion)
{
//Gibt es eine Letzte Stelle, sonst default
if (!criterion.IsNullOrEmptyOrDbEmpty())
{
//Wir entscheiden mit der letzten Stelle der LE, welcher LE-Consumer verbucht oder ob der default Consumer das tun muss
string endOfLe = criterion.Substring(criterion.Length - 1);
//Der Name des Consumer endet mit dem gleichen Zeichen
if (consumersWithDemand.Any(c => c.EndsWith(endOfLe)))
{
return consumersWithDemand.First(c => c.EndsWith(endOfLe));
}
//Soll dieses Message von einem speziellen Consumer bearbeitet werden? Aber dieser ist beschäftigt
else if (Regex.IsMatch(endOfLe, "[0-9]"))
{
//Message auslassen - keinem Consumer zuordnen
return null;
}
//HU endet mit einem Zeichen, dass zu keinem Consumer passt also Default oder auslassen
else
{
//Default Consumer muss mit Default enden!
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
}
}
else
{
//Default Consumer muss mit Default enden!
return consumersWithDemand.FirstOrDefault(c => c.EndsWith("Default"));
}
}
#endregion Private Methods
#region Protected Methods
protected override Dictionary<string, List<IHostMessage>> GetDataForConsumers(List<string> consumersWithDemand)
{
//TODO: jub evtl. die Messagem Stau Meldung aus KW übernehmen
Dictionary<string, List<IHostMessage>> dataForConsumers = new Dictionary<string, List<IHostMessage>>();
try
{
using HostEntities db = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create();
if (consumersWithDemand.Count > 0)
{
List<IHostMessage> hostMessageEntries;
//es werden maximal so viele Messages abgerufen, wie ein einzelner Consumer annehmen könnte, damit das TryAdd sicher klappt und der Status nicht fälschlich
//gesetzt wird
if (_firstExecution)
{
//beim ersten mal nach Neustart werden auch die InProgress Messages nochmal mit abgerufen
//Ref == null damit nur Kopfnachrichten gefunden werden
hostMessageEntries = _serviceFromWms.GetAllEntries(a => a.Status == "Pending" || a.Status == "InProgress", takeCount: _consumerQueueLength).OrderBy(a => a.Id).ToList();
_firstExecution = false;
}
else
{
//sonst nur Messages, die noch an keinen Consumer gegeben wurden
//Ref == null damit nur Kopfnachrichten gefunden werden
hostMessageEntries = _serviceFromWms.GetAllEntries(a => a.Status == "Pending", takeCount: _consumerQueueLength).OrderBy(a => a.Id).ToList();
}
if (hostMessageEntries.Any())
{
//ohne LoadBalancing erhält der erste Consumer alle Messages zum Verbuchen
if (!_useLoadBalancing)
{
//der Consumer muss also alle Messages verarbeiten und nicht nur loggen
dataForConsumers.Add(consumersWithDemand.FirstOrDefault() ?? string.Empty, hostMessageEntries);
//Status auf InProgress damit jede Message nur einmal abgerufen wird
hostMessageEntries.ForEach(message => message.Status = "InProgress");
Log.Write(LogLevel.Low, $"Kein LoadBalancing aktiv, {hostMessageEntries.Count} neue HostMessages für Consumer {consumersWithDemand.FirstOrDefault()} gefunden");
}
//mit LoadBalancing wird nach der letzen Ziffer der ersten HU der Message auf 10 Consumer verteilt,
//enthält die Message keine HU wird es an Consumer 11 übergeben.
else
{
Log.Write(LogLevel.Low, $"LoadBalancing aktiv, {hostMessageEntries.Count} neue HostMessages gefunden, verteile auf Consumer");
foreach (IHostMessage message in hostMessageEntries)
{
try
{
FromWms entityFromWms = HostMessageFromWmsService.HostMapper.Map<FromWms>(message);
Log.Write(LogLevel.Low, $"Producerschleife für {entityFromWms}");
string consumer;
//keine HU in der Message, dann dem Default Consumer zuordnen
if (entityFromWms.LeNo.IsNullOrEmptyOrDbEmpty())
{
consumer = GetConsumerForBooking(consumersWithDemand, null);
//wenn der Default Consumer nicht in der Liste war, Message auslassen
if (consumer != null)
{
AddDataForConsumer(ref dataForConsumers, consumer, message);
//Status auf InProgress damit die Message nur einmal abgerufen wird
entityFromWms.Status = "InProgress";
}
}
//Messages mit HU
else
{
consumer = GetConsumerForBooking(consumersWithDemand, entityFromWms.LeNo);
AddDataForConsumer(ref dataForConsumers, consumer, message);
//Status auf InProgress damit die Message nur einmal abgerufen wird
entityFromWms.Status = "InProgress";
}
}
catch (Exception ex)
{
Log.WriteException(ex);
}
}
Log.Write(LogLevel.Low, $"Producerschleife beendet");
}
//Status Updates für alle weitergereichten Messages
db.SaveChanges();
}
else
{
Log.Write(LogLevel.Debug, 60, "Keine Messages in FromWms");
}
}
else
{
Log.Write(LogLevel.Debug, 60, "Kein Consumer hat demand");
}
return dataForConsumers;
}
catch (Exception e)
{
Log.WriteException(e);
return dataForConsumers;
}
}
#endregion Protected Methods
#region Public Constructors
public FromWmsBookingProducer(int workinterval, bool useLoadBalancing, int consumerQueueLength, IUnityContainer unityContainer) : base(typeof(FromWmsBookingProducer).Name, workinterval, true)
{
_unityContainer = unityContainer;
_serviceFromWms = _unityContainer.Resolve<IHostMessageFromWmsService>();
_consumerQueueLength = consumerQueueLength;
_useLoadBalancing = useLoadBalancing;
}
#endregion Public Constructors
}
}

View File

@@ -0,0 +1,17 @@
using System;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
public class FromWmsException : Exception
{
public FromWmsException(string message) : base(message)
{
}
public FromWmsException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System.Xml.Serialization;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Helpers;
using Model = Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces
{
public interface IHandleRecord<in THostMessage> where THostMessage : IHostMessage
{
bool Handle(THostMessage arg);
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.LoopStrategy;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageStrategies.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
public class AcknowledgeTransportCompletedHandler : IHandleRecord<IAcknowledgeTransportCompleted>
{
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly ITransportOrderService _transportOrderService;
private readonly ILeService _leService;
private readonly IDestinationService _destinationService;
private readonly ILoopSelectionService _loopSelectionService;
public AcknowledgeTransportCompletedHandler(IWcsDbContextFactory dbContextFactory, ITransportOrderService transportOrderService, ILeService leService, IDestinationService destinationService, ILoopSelectionService loopSelectionService)
{
_dbContextFactory = dbContextFactory;
_transportOrderService = transportOrderService;
_destinationService = destinationService;
_leService = leService;
_loopSelectionService = loopSelectionService;
}
public bool Handle(IAcknowledgeTransportCompleted message)
{
using (IWcsDbContext db = _dbContextFactory.GetDbContext())
{
List<int> ordersHostIds = db.OrdersHost.Where(o => o.IdOrderWms == message.IdOrderWms).Select(o => o.Id).ToList();
if (ordersHostIds.Any())
{
if (ordersHostIds.Count() > 1)
{
throw new InvalidOperationException($"Multiple OrdersHost found for same WMS id {message.IdOrderWms}");
}
_transportOrderService.FinishOrdersHost(ordersHostIds.Single(), "AcknowledgeTransportCompleted");
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id {ordersHostIds.Single()} and WMS id {message.IdOrderWms} finished.");
//Other open OH for HU that must be cancelled?
List<OrdersHost> others = db.OrdersHost
.OpenByLeNo(message.LeNo).Where(oh => oh.Type == TransportOrderType.Transport).ToList();
foreach (var other in others)
{
_transportOrderService.CancelOrdersHost(other.Id, $"New destination {message.Destination}", $"OrderHost {ordersHostIds.Single()} was finished");
}
}
else
{
Log.Write(LogLevel.Error, $"{nameof(AcknowledgeTransportCompletedHandler)}: Cannot find OH for WMS id {message.IdOrderWms}");
}
db.SaveChanges();
}
// New DB Context since OrdersConveyorService changes data that we otherwise wouldn't notice
using (IWcsDbContext db = _dbContextFactory.GetDbContext())
{
// le empty?
Le le = db.Le.ByLeNo(message.LeNo);
// If there is no other order for this le, send it back to the storage
if (!db.OrdersHost.OpenByLeNo(message.LeNo).Any() && le != null)
{
DestinationProperty workStation = db.DestinationProperty.FirstOrDefault(d => d.Name == message.Destination && d.Property == nameof(IDestinationProperties.Level));
var workStationDest = _destinationService.Get(message.Destination);
List<VLoopOverview> availableLoops = _loopSelectionService.GetSuitableStorageLoops(message.LeNo);
// Choose a desired destination based on which is the closest to the workstation
string destination = MfcAllDestinations.StorageLoop2;
if (workStationDest != null)
{
destination = workStationDest.StorageArea == MfcAllDestinations.AKL02 ? MfcAllDestinationsOldSystem.LOOP3 : workStationDest.Level == ConveyorLevel.EtraBox.ToString() ? MfcAllDestinations.StorageLoop1 : MfcAllDestinations.StorageLoop2;
// If only one of the loops is available, choose this, no matter where the LE is located
if (availableLoops.Count == 1)
{
destination = availableLoops.First().PlcName;
}
else
{
// Prefer the destination closest connected to the pick station but select the other if this is full
string fallbackDestination = workStationDest.StorageArea == MfcAllDestinations.AKL02 ? MfcAllDestinationsOldSystem.LOOP3 : workStationDest.Level == ConveyorLevel.EtraBox.ToString() ? MfcAllDestinations.StorageLoop1 : MfcAllDestinations.StorageLoop2;
if (!_loopSelectionService.hasEnoughSpace(destination) && _loopSelectionService.hasEnoughSpace(fallbackDestination))
{
destination = fallbackDestination;
}
}
}
string source = message.Destination == MfcAllDestinationsOldSystem.TOPUP ? MfcAllDestinationsOldSystem.TOPUP : message.Source ?? "?";
int ordersHostId = _leService.CreateTransport(message.LeNo, source, destination);
Log.Write(LogLevel.Info, $"Loop selection in acknowledge transport handler for {le.LeNo}, set to {destination}");
LoopStrategy.ReserveForLoop(le.LeNo, ordersHostId, availableLoops.FirstOrDefault(l => l.PlcName == destination)?.Name);
}
}
return true;
}
}
}

View File

@@ -0,0 +1,147 @@
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
public class DepartureNotificationHandler : IHandleRecord<IDepartureNotification>
{
private readonly IDestinationService _destinationService;
private readonly ITransportOrderService _transportOrderService;
private readonly IWcsDbContextFactory _wcsDbContextFactory;
public DepartureNotificationHandler(IDestinationService destinationService, ITransportOrderService transportOrderService, IWcsDbContextFactory wcsDbContextFactory)
{
_destinationService = destinationService;
_transportOrderService = transportOrderService;
_wcsDbContextFactory = wcsDbContextFactory;
}
public bool Handle(IDepartureNotification message)
{
using IWcsDbContext db = _wcsDbContextFactory.GetDbContext();
// If we don't have the LE, we need to depart. Otherwise we can't get rid of it.
if (db.Le.ByLeNo(message.LeNo) == null || message.LeNo.StartsWith("B") || message.StorageArea.ToLower() == "dummy")
{
ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position);
//For EtraBoxes we can Finish the OrdersHost here As there is no feedback anymore for this boxes:
if (message.LeNo.StartsWith("B") && message.Position.Contains("PTL"))
{
//Finish all TransportOrders from this PTL Place as the place is empty now.
var etraBoxOrder = db.OrdersHost.ByStatus(TransportOrderStatus.Transmitted).Where(o => o.Source == message.Position);
if (etraBoxOrder.Any())
{
foreach (var oh in etraBoxOrder)
{
oh.Finish();
if (oh.LeNo == message.LeNo)
{
Log.Write(LogLevel.Info, $"EtraBox {oh.LeNo} is pushed off, finish orders host {oh.Id}.");
}
else
{
oh.Info = $"CleanedUp by Departure for {message.LeNo}";
Log.Write(LogLevel.Info, $"EtraBox {oh.LeNo} orders host {oh.Id} is finished because EtraBox {message.LeNo} was pushed off from the same PTL place.");
}
}
}
}
db.SaveChanges();
return true;
}
var openOH = db.OrdersHost.OpenByLeNo(message.LeNo).ToList();
if (!openOH.Any())
{
//Receive and Replenishment places don't need to create a WCS Transport.
if(message.Position.StartsWith(Constants.MfcAllDestinationsOldSystem.IPT01.Substring(0,3)) || message.Position.StartsWith(Constants.MfcAllDestinations.RCV01.Substring(0, 3)) || message.Position== Constants.MfcAllDestinationsOldSystem.TOPUP)
{
//Ignore and go on
return true;
}
OrdersHost ordersHost = OrdersHostFactory.GetInstance()
.InitialForLe(message.LeNo, TransportOrderType.Transport, message.Position, _destinationService.GetDefaultStorage(message.Position));
db.Add(ordersHost);
// Always depart a box on the error stations to prevent blocks
IDestinationProperties destination = _destinationService.Get(message.Position);
if (destination != null && destination.IsNio)
{
ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position);
ordersHost.Le.SetStatus(LeStatus.OnConveyor);
}
Log.Write(LogLevel.Info, $"Added OrdersHost for {message.LeNo} from {message.Position} to {ordersHost.Destination}.");
}
else
{
// Is there an order to this place InProgress or InDestinationZone? => Start again by setting this order to pending
var currentOrderToPlace = db.OrdersHost
.ByLeNo(message.LeNo)
.ByDestination(message.Position)
.ByStatus(TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone)
.FirstOrDefault();
if (currentOrderToPlace != null)
{
if (currentOrderToPlace.Status == TransportOrderStatus.InDestinationZone)
{
IDestinationProperties destination = _destinationService.Get(currentOrderToPlace.Destination);
if (currentOrderToPlace.HostDestination != null)
{
currentOrderToPlace.Destination = destination.ConnectedSequencer;
}
ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position);
currentOrderToPlace.UpdateResources(TransportOrderStatus.InProgress, null);
currentOrderToPlace.ForceSetStatusInProgress();
currentOrderToPlace.Le.SetStatus(LeStatus.OnConveyor);
db.SaveChanges();
Log.Write(LogLevel.Info, $"OrdersHost for {message.LeNo} to {message.Position} started again by DepartureNotification.");
}
}
else
{
//Todo: Wouldn't it be better to do this in StartInitialOrders?
var otherPicOrder = db.OrdersHost.ByLeNo(message.LeNo)
.ByStatus(TransportOrderStatus.Initial)
.ApplyWmsOrdering()
.FirstOrDefault();
if (otherPicOrder != null)
{
_transportOrderService.StartNextTransport(otherPicOrder.Id);
}
//Set source to TOPUP in case Departure has been sent from TopUP or IPT stations, to be able to accept the box on scale
if (message.Position == Constants.MfcAllDestinationsOldSystem.TOPUP || message.Position.StartsWith(Constants.MfcAllDestinationsOldSystem.IPT01.Substring(0, 3)))
{
var activeOH = openOH.Where(o=>o.Status == TransportOrderStatus.InProgress).FirstOrDefault();
if (activeOH != null)
{
activeOH.Source = Constants.MfcAllDestinationsOldSystem.TOPUP;
}
}
else
{
_destinationService.SetLeRequestDeparture(message.LeNo, message.Position);
ConveyorTelegrams.SendDepartureEtra(db, message.LeNo, message.Position);
db.Le.FirstOrDefault(l => l.LeNo == message.LeNo)?.SetStatus(LeStatus.OnConveyor);
}
db.SaveChanges();
return true;
}
}
// update destination status!
_destinationService.SetLeRequestDeparture(message.LeNo, message.Position);
db.SaveChanges(true);
return true;
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Constants;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
public class HuChangeHandler : IHandleRecord<IHuChange>
{
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly LeFactory _leFactory = LeFactory.GetInstance();
public HuChangeHandler(IWcsDbContextFactory dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public bool Handle(IHuChange message)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
Le le = db.Le.ByLeNo(message.LeNo);
if (le == null)
{
Log.Write(LogLevel.Info, $"HuChange - HU {message.LeNo} is unknown. Create HU...");
LeTypeName type = LeType.GetLeTypeFromLeNo(message.LeNo);
if (type == null)
{
throw new LeNoWrongFormatException($"HuChange - The prefix of {message.LeNo} is not known.");
}
le = db.Add(_leFactory.RegularLe(message.LeNo, type)).Entity;
db.SaveChanges();
}
UpdateLeTypeIfGiven(message, le, db);
le.IsEmpty = message.IsLeEmpty ?? true;
le.AbcArea = !le.IsEmpty ? SetAbcArea(message, le) : null;
le.Subdivision = message.Subdivision?.ToEnumFromDescription<SubdivisionType>() ?? le.Subdivision;
if (le.IsEmpty)
{
le.Weight = le.LeType.TareWeight; // Since there is no scale in the upper loops
le.ArticleTag = null;
}
else
{
le.ArticleTag = message.ArticleTag;
}
db.SaveChanges();
return true;
}
private static string SetAbcArea(IHuChange message, Le le)
{
if (message.AbcArea == null)
{
return le.AbcArea;
}
if (!Area.IsValid(message.AbcArea) && !Area.IsClassificationForOldStorage(message.AbcArea))
{
return Area.Z;
}
return message.AbcArea;
}
private static void UpdateLeTypeIfGiven(IHuChange message, Le le, IWcsDbContext db)
{
if (message.LeType == null)
{
return;
}
if (!Enum.IsDefined(typeof(LeTypeName), message.LeType))
{
throw new InvalidOperationException($"LeType={message.LeType} does not match an enum. Message: {message}");
}
LeType leType = db.LeType.SingleOrDefault(l => l.Name == (LeTypeName)Enum.Parse(typeof(LeTypeName), message.LeType));
if (leType == null)
{
throw new InvalidOperationException($"LeType={message.LeType} does not match exactly one DB entry -. Message: {message}");
}
le.SetLeType(leType);
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper.Configuration.Conventions;
using Gebhardt.Shared;
using Gebhardt.Shared.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.Wcs.InterfaceOldPlc.Services;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.EntityFramework;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Microsoft.EntityFrameworkCore;
using static Gebhardt.StoreWare.Wcs.Common.ConstantsCommon;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
public class ShipmentTransportOrderHandler : IHandleRecord<IShipmentTransportOrder>
{
private readonly IDestinationService _destinationService;
private readonly ITransportOrderService _transportOrderService;
private IShipmentTransportOrder _shipmentTransportOrder;
private readonly LeFactory _leFactory = LeFactory.GetInstance();
private readonly List<string> _allStorageDestinations;
public ShipmentTransportOrderHandler(IDestinationService destinationService, ITransportOrderService transportOrderService)
{
_destinationService = destinationService;
_transportOrderService = transportOrderService;
_allStorageDestinations = _destinationService.Where(d => d.IsStorage).Select(d => d.Name).ToList();
}
public bool Handle(IShipmentTransportOrder message)
{
_shipmentTransportOrder = message;
using WcsDbContext db = new();
CheckTransportOrder(db);
string cartonNo = _shipmentTransportOrder.LeNo;
Le le = db.Le.ByLeNo(cartonNo);
if (le == null)
{
Log.Write(LogLevel.Info, $"Carton number {cartonNo} is unknown. Creating a new carton...");
// TODO: Add correct prefixes depending on which type of carton? Now it is set as unknown
LeTypeName type = LeType.GetLeTypeFromLeNo(cartonNo);
le = db.Add(_leFactory.RegularLe(cartonNo, type)).Entity;
db.SaveChanges();
}
le.SetStatus(LeStatus.Created);
CreateTransportOrder();
db.SaveChanges();
return true;
}
private void CheckTransportOrder(IWcsDbContext db)
{
if (_shipmentTransportOrder.LeNo == null)
{
throw new FromWmsException("The carton or EtraBox number is missing.");
}
if(_shipmentTransportOrder.Destination == null)
{
throw new FromWmsException("No destination set.");
}
if (_shipmentTransportOrder.LeNo.StartsWith("B"))
{
List<string> allowedEtraBoxDestinations = new List<string>() {"D01", "D02", "D03", "D04", "D05", "D06", "D07", "D08", "D09", "D99", "M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12" };
if(_shipmentTransportOrder.Destination.Split('/').ToList().Any(d => !allowedEtraBoxDestinations.Contains(d)))
{
throw new FromWmsException("Not allowed destination for EtraBox.");
}
}
else
{
HostEntities dbHost = new HostEntitiesFactory(HostEntities.DefaultConnectionStringName).Create(); ;
var allowedDestinationsForCartons = dbHost.RouteSetting.Select(r => r.WmsRouteName).Distinct().ToList();
if (!allowedDestinationsForCartons.Contains(_shipmentTransportOrder.Destination))
{
throw new FromWmsException("Not allowed destination for Carton/Paperbag/Pallet.");
}
}
//Check if there is already an open order for the same LE (WMS is sending for each Pick)
var openOrdes = db.OrdersHost.ByLeNo(_shipmentTransportOrder.LeNo).ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.Initial, TransportOrderStatus.Transmitted).Where(o=>o.Source == _shipmentTransportOrder.Source && o.Destination == _shipmentTransportOrder.Destination);
if(openOrdes.Any())
{
throw new FromWmsException("Order is already created.");
}
else
{
//Also check Finished orders from the last 2 Minutes, because WMS sometimes sends Departure before the TransportOrder...
var age = DBDateTime.Now - TimeSpan.FromMinutes(2);
openOrdes = db.OrdersHost.ByLeNo(_shipmentTransportOrder.LeNo).ByStatus(TransportOrderStatus.Finished).Where(o => o.Source == _shipmentTransportOrder.Source && o.Destination == _shipmentTransportOrder.Destination && o.Timestamp > age);
if (openOrdes.Any())
{
throw new FromWmsException("Order is already created.");
}
}
}
private void CreateTransportOrder()
{
using WcsDbContext db = new();
string source = _shipmentTransportOrder.Source;
OrdersHost ordersHost = OrdersHostFactory.GetInstance().InitialForLe(_shipmentTransportOrder.LeNo, TransportOrderType.TransportHost, source == TestToolConstants.TestToolSource ? Common.Constants.MfcAllDestinations.AKL01 : source, _shipmentTransportOrder.Destination);
db.Add(ordersHost);
//Only for Etra boxes we transmitt the order here. The other orders are started at the checkResult Telegram.
if (ordersHost.Le.LeType.Name == LeTypeName.EtraBox)
{
//Send Tord to Old PLC if allowed in Settings
SettingsManager.GetParsedValue(SettingNames.SendTordToOldPlc, out bool sendTord, false, true);
if (sendTord)
{
SendToOldPlcService.SendTord(ordersHost.LeNo, ordersHost.Destination.Split('/').ToList());
}
//We set the Status to transmitted here because we have already sent the telegram and we don't want ConveyorDispo to start those orders (OrdersConveyor is not needed, as there will be no feedback from PLC)
ordersHost.ForceSetStatusTransmitted();
//The EtraBox order will be closed as soon as we get the Departure message from WMS
}
db.SaveChanges();
}
}
}

View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AutoMapper.Configuration.Conventions;
using Gebhardt.Shared;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Microsoft.EntityFrameworkCore;
using static Gebhardt.StoreWare.Wcs.Common.ConstantsCommon;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
public class TransportOrderHandler : IHandleRecord<ITransportOrder>
{
private readonly IDestinationService _destinationService;
private readonly ITransportOrderService _transportOrderService;
private ITransportOrder _transportOrder;
private readonly LeFactory _leFactory = LeFactory.GetInstance();
private readonly List<string> _allStorageAndEmptyLeBufferDestinations;
public TransportOrderHandler(IDestinationService destinationService, ITransportOrderService transportOrderService)
{
_destinationService = destinationService;
_transportOrderService = transportOrderService;
_allStorageAndEmptyLeBufferDestinations = _destinationService.Where(d => d.IsStorage || d.IsStorageArea || d.BuffersEmptyLe).Select(d => d.Name).ToList();
}
public bool Handle(ITransportOrder message)
{
_transportOrder = message;
using WcsDbContext db = new();
string leNo = _transportOrder.LeNo;
Le le = db.Le.ByLeNo(leNo);
if (le == null)
{
Log.Write(LogLevel.Info, $"HU {leNo} is unknown. Create HU...");
LeTypeName type = LeType.GetLeTypeFromLeNo(leNo);
le = db.Add(_leFactory.RegularLe(leNo, type, preferredStorageArea: message.StorageArea)).Entity;
db.SaveChanges();
}
CheckTransportOrder();
IDestinationProperties destinationProperties = _destinationService.Get(_transportOrder.Destination);
if (destinationProperties == null)
{
throw new TransportDestinationInvalidException($"Unknown destination in {message}");
}
if (destinationProperties.IsStorage && !destinationProperties.IsSequencer && le.IsInStorage())
{
throw new LeAlreadyInStorageException($"{le} is in storage.");
}
CheckLeIsReachable(le);
//CheckActiveOrdersForLe();
if (_transportOrder.MovementType == TransportOrderType.Storage.ToString())
{
le.SetStatus(LeStatus.Created);
}
else
{
SetTransportStatus(le);
}
SetAbcArea(le);
// Le is in the old system, but no movement file has been created for it.
bool leNeedsMomFile = le.IsHuInOldSystem == true && le.L8OrderCreated == null;
CreateTransportOrder(leNeedsMomFile);
db.SaveChanges();
return true;
}
private void CheckTransportOrder()
{
if (_transportOrder.LeNo == null)
{
throw new LeNoMissingException("The HU number is is missing.");
}
if (_destinationService.Get(_transportOrder.Destination) == null)
{
throw new TransportDestinationInvalidException($"Destination {_transportOrder.Destination} is unknown. Order is confirmed negatively.");
}
if (string.IsNullOrEmpty(_transportOrder.MovementType))
{
throw new FromWmsException($"Field {nameof(_transportOrder.MovementType)} is not set or not known.");
}
// TODO: Solche Prüfungen sollten in einem Plausiblitätsmodul für LE-Nummern erfolgen, damit sie leicht kundenspezifisch angepasst werden können
if (_transportOrder.LeNo.Length < ConstantsLE.LengthBarcodeWithType)
{
throw new LeNoTooShortException($"HU number '{_transportOrder.LeNo}' is too short. Should be {ConstantsLE.LengthBarcodeWithType} digits");
}
if (_transportOrder.LeNo.Length > ConstantsLE.LengthBarcodeWithType)
{
throw new LeNoTooLongException($"HU number '{_transportOrder.LeNo}' is too long. Should be {ConstantsLE.LengthBarcodeWithType} digits");
}
}
private void SetAbcArea(Le le)
{
//le.AbcArea = _transportOrder.AbcArea;
}
private void CreateTransportOrder(bool oldSystemBoxRequiringMomFile = false)
{
using WcsDbContext db = new();
string source = _transportOrder.Source;
OrderTypeWms? typeWmsForOrderHost;
if (!Enum.TryParse(_transportOrder.MovementType, true, out OrderTypeWms typeWms))
{
Log.Write(LogLevel.Error, $"could not parse '{_transportOrder.MovementType}'");
typeWmsForOrderHost = null;
}
else
{
typeWmsForOrderHost = typeWms;
}
OrdersHost ordersHost = OrdersHostFactory.GetInstance().InitialForLe(_transportOrder.LeNo, TransportOrderType.TransportHost, source == TestToolConstants.TestToolSource ? Common.Constants.MfcAllDestinations.AKL01 : source,
_transportOrder.Destination, idOrderWms: _transportOrder.IdOrderWms, idOrderWmsHead: _transportOrder.IdOrderWmsHead, idOrderWmsPos: _transportOrder.IdOrderWmsPos, typeWms: typeWmsForOrderHost);
IDestinationProperties destination = _destinationService.Get(_transportOrder.Destination);
if (destination != null && destination.IsAfterSequencer)
{
ordersHost.Destination = destination.ConnectedSequencer;
ordersHost.HostDestination = _transportOrder.Destination;
}
// TODO 15585
ordersHost.SequenceWms = _transportOrder.Sequence;
ordersHost.IsDirectPicking = _transportOrder.IsDirectPicking;
ordersHost.PriorityDate = _transportOrder.Priority;
db.Add(ordersHost);
if (source == TestToolConstants.TestToolSource)
{
ordersHost.Priority = TestToolConstants.TestToolPriority;
}
if (oldSystemBoxRequiringMomFile)
{
try
{
CreateMomFile(ordersHost.LeNo);
ordersHost.Le.L8OrderCreated = DateTime.Now;
Log.Write(LogLevel.Info, $"Mom file created and saved for HU {ordersHost.LeNo}.");
}
catch (Exception e)
{
Log.Write(LogLevel.Error, $"Error creating mom file for HU {ordersHost.LeNo}, Exception: {e}");
}
}
db.SaveChanges();
}
private void CreateMomFile(string huNumber)
{
// Hardcoded since very temporary
string filePath = $@"\\10.101.71.32\\InterfaceFiles\\ProdGB\\OrdersGB\\{huNumber}_{DateTime.Now:yyyyMMddHHmmss00}.mom";
File.WriteAllText(filePath, huNumber);
}
/// <summary>
/// A Handling Unit that rests at a workplace is set to "On conveyer" when it receives a transport order
/// </summary>
private void SetTransportStatus(Le le)
{
if (le.Status != LeStatus.InStorage)
{
le.SetStatus(LeStatus.OnConveyor, le.LastWhere);
}
}
private void CheckLeIsReachable(Le le)
{
if (le.IsInStorage())
{
bool? storageDeviceHasCriticalFault = new WcsDbContext().AisleForLe.GetAisle(le.AisleName, le.StorageArea)?.StorageDevices.Any(s => s.HasCriticalFault);
if (storageDeviceHasCriticalFault == null)
{
Log.Write(LogLevel.Error, $"Unknown aisle {le.AisleName} LE: {le.LeNo}.");
}
else if (storageDeviceHasCriticalFault.Value)
{
// This throw causes, that the TransportOrder in FromWms will be frozen in status 'Failed'.
// Maybe the order gets then forgotten.
Log.Write(LogLevel.Error, $"Order for Le {le.LeNo} will be delayed: {le.AisleName} has a critical fault.");
}
}
}
private void CheckActiveOrdersForLe()
{
//is the an order to a storage location or EMB?
if (_allStorageAndEmptyLeBufferDestinations.Contains(_transportOrder.Destination))
{
using WcsDbContext db = new();
OrdersHost ordersHost = db.OrdersHost.AsNoTracking().
ByLeNo(_transportOrder.LeNo).
Open().
ByDestination(_allStorageAndEmptyLeBufferDestinations).FirstOrDefault();
if (ordersHost != null)
{
// Replace all open orders to storage or EMB with the new destination
_transportOrderService.CancelOrdersHost(ordersHost.Id, $"OH on the way to storage or EMB was ordered to a commisioning destination. This order is cancelled and replaced by this new host order with IdOrderWms {_transportOrder.IdOrderWms}.");
Log.Write(LogLevel.Info, $"HU {ordersHost.LeNo} with destination EMB or storage was ordered to a commisioning destination. The previous OH with IdOrderWms {ordersHost.IdOrderWms} was cancelled");
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
//-----------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by HostInterfaceConfigurator Version 17.2022.322.1138
// Generated at 28.03.2022 09:44:04 (Universal Time)
//
// This file can be changed to adapt it to project needs.
// If any file is changed, do not run HostInterfaceConfigurator with answer "Yes"
// to dialog question "Do you want to overwrite existing files?"
// Questions and proposals please address to "StoreWare Development Team"
// </auto-generated>
//-----------------------------------------------------------------------------------------
using System;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.Shared;
using Gebhardt.StoreWare.WcsWms.Constants;
using Gebhardt.StoreWare.Wcs.Common.DbAccess;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation
{
public partial class UnsupportedHostMessageHandler : IHandleRecord<UnsupportedHostMessage>
{
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly IHostMessageFromWmsService _serviceFromWms;
private readonly IHostMessageToWmsService _serviceToWms;
public UnsupportedHostMessageHandler(IWcsDbContextFactory dbContextFactory, IHostMessageFromWmsService serviceFromWms, IHostMessageToWmsService serviceToWms)
{
_dbContextFactory = dbContextFactory;
_serviceFromWms = serviceFromWms ?? new HostMessageFromWmsService();
_serviceToWms = serviceToWms ?? new HostMessageToWmsService();
}
public bool Handle(UnsupportedHostMessage message)
{
message.Handle();
throw new Exception(message.ErrorInterface ?? "UnsupportedHostMessage - Wrong message type");
//using IWcsDbContext db = _dbContextFactory.GetDbContext();
//TODO - use custom code hier
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,33 @@
using Unity;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.MessageImplementation;
using Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Interfaces;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Models;
using Gebhardt.StoreWare.WcsWms.InterfaceWcsWms.Services;
namespace Gebhardt.StoreWare.Wcs.HostBooking.InterfaceWcsWms
{
public static class MessageInitializer
{
public static void RegisterFromWmsServices(this IUnityContainer unityContainer)
{
unityContainer.RegisterType<IHostMessageFromWmsService, HostMessageFromWmsService>();
}
public static void RegisterFromWmsHandlers(this IUnityContainer unityContainer)
{
unityContainer.RegisterType<IHandleRecord<TransportOrder>, TransportOrderHandler>("TransportOrder");
unityContainer.RegisterType<IHandleRecord<ShipmentTransportOrder>, ShipmentTransportOrderHandler>("ShipmentTransportOrder");
unityContainer.RegisterType<IHandleRecord<AcknowledgeTransportCompleted>, AcknowledgeTransportCompletedHandler>("AcknowledgeTransportCompleted");
unityContainer.RegisterType<IHandleRecord<DepartureNotification>, DepartureNotificationHandler>("DepartureNotification");
//unityContainer.RegisterType<IHandleRecord<RequestEmptyHuReport>, RequestEmptyHuReportHandler>("RequestEmptyHuReport");
unityContainer.RegisterType<IHandleRecord<HuChange>, HuChangeHandler>("HuChange");
}
}
}

View File

@@ -0,0 +1,28 @@
namespace Gebhardt.StoreWare.Wcs.HostBooking.Properties {
// This class allows you to handle specific events on the settings class:
// The SettingChanging event is raised before a setting's value is changed.
// The PropertyChanged event is raised after a setting's value is changed.
// The SettingsLoaded event is raised after the setting values are loaded.
// The SettingsSaving event is raised before the setting values are saved.
internal sealed partial class Settings {
public Settings() {
// // To add event handlers for saving and changing settings, uncomment the lines below:
//
// this.SettingChanging += this.SettingChangingEventHandler;
//
// this.SettingsSaving += this.SettingsSavingEventHandler;
//
}
private void SettingChangingEventHandler(object sender, System.Configuration.SettingChangingEventArgs e) {
// Add code to handle the SettingChangingEvent event here.
}
private void SettingsSavingEventHandler(object sender, System.ComponentModel.CancelEventArgs e) {
// Add code to handle the SettingsSaving event here.
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB