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

Binary file not shown.

View File

@@ -1,9 +0,0 @@
## Responsibillities of the tables and processes
- HostBooking processes FromWms entries and creates OrdersHost entries with status Initial
- ConveyorDispo processes OrdersHost entries set to Initial and sets it to Pending
- ConveyorDispo processes Pending OrdersHost entries and creates OrdersConveyor entries and/or OrdersMiniload. If a Handling Unit is on the Conveyor OrdersConveyor is needed. If it is in storage first OrdersMiniload is needed to get it out of storage and then OrdersConveyor is created.
- ConveyorDispo creates transport order telegrams from OrdersConveyor
- Src/OlsDispo creates transport order telegrams from OrdersMiniload
- CommunicationPorcess transmits / receives TCP/IP telegrams to / from PLC's conveyor and storage devices
- ConveyorBooking / Src/OlsBooking processes telegrams and set the order status of OrdersConveyor / OrdersMiniload

View File

@@ -1,305 +0,0 @@
using Gebhardt.Shared;
using System.Collections.Generic;
using System.Linq;
namespace Gebhardt.StoreWare.Wcs.Common
{
#region Send Methoden
/// <summary>
/// Klasse dient zum Senden von Telegrammen an die SPS
/// </summary>
public static partial class ConveyorTelegrams
{
private static List<string> destinationsOldSystem = new List<string>() {
Constants.MfcAllDestinationsOldSystem.ETXBU,
Constants.MfcAllDestinationsOldSystem.ETXBU_M,
Constants.MfcAllDestinationsOldSystem.ETXBU_S,
Constants.MfcAllDestinationsOldSystem.IPT01,
Constants.MfcAllDestinationsOldSystem.IPT02,
Constants.MfcAllDestinationsOldSystem.IPT03,
Constants.MfcAllDestinationsOldSystem.IPT01,
Constants.MfcAllDestinationsOldSystem.REP01,
Constants.MfcAllDestinationsOldSystem.REP02,
Constants.MfcAllDestinationsOldSystem.REP03,
Constants.MfcAllDestinationsOldSystem.ETM01,
Constants.MfcAllDestinationsOldSystem.ETM02,
Constants.MfcAllDestinationsOldSystem.ETM03,
Constants.MfcAllDestinationsOldSystem.ETS01,
Constants.MfcAllDestinationsOldSystem.ETS02,
Constants.MfcAllDestinationsOldSystem.ETS03,
Constants.MfcAllDestinationsOldSystem.QCS01,
Constants.MfcAllDestinationsOldSystem.QCS02,
Constants.MfcAllDestinationsOldSystem.ERR01,
Constants.MfcAllDestinationsOldSystem.TOPUP,
//Constants.MfcAllDestinationsOldSystem.ERS01,
Constants.MfcAllDestinationsOldSystem.LOOP3,
Constants.MfcAllDestinationsOldSystem.C0101,
Constants.MfcAllDestinationsOldSystem.C0201,
Constants.MfcAllDestinationsOldSystem.S0300,
Constants.MfcAllDestinationsOldSystem.S0301,
Constants.MfcAllDestinationsOldSystem.S0302,
Constants.MfcAllDestinationsOldSystem.S0303,
Constants.MfcAllDestinationsOldSystem.S0304,
Constants.MfcAllDestinationsOldSystem.S0305,
Constants.MfcAllDestinationsOldSystem.S0400,
Constants.MfcAllDestinationsOldSystem.S0401,
Constants.MfcAllDestinationsOldSystem.S0402,
Constants.MfcAllDestinationsOldSystem.S0403,
Constants.MfcAllDestinationsOldSystem.S0404,
Constants.MfcAllDestinationsOldSystem.S0405,
//Scanner Replenishment
Constants.MfcScannerOldSystem.SC101,
Constants.MfcScannerOldSystem.SC102,
Constants.MfcScannerOldSystem.SC103,
Constants.MfcScannerOldSystem.SC104,
Constants.MfcScannerOldSystem.SC105,
Constants.MfcScannerOldSystem.SC106,
Constants.MfcScannerOldSystem.SC107,
//Scanner Loop+Cranes
Constants.MfcScannerOldSystem.SC201,
Constants.MfcScannerOldSystem.SC202,
Constants.MfcScannerOldSystem.SC203,
Constants.MfcScannerOldSystem.SC204,
Constants.MfcScannerOldSystem.SC205,
Constants.MfcScannerOldSystem.SC206,
Constants.MfcScannerOldSystem.SC207,
Constants.MfcScannerOldSystem.SC208,
Constants.MfcScannerOldSystem.SC209,
Constants.MfcScannerOldSystem.SC210,
Constants.MfcScannerOldSystem.SC211,
Constants.MfcScannerOldSystem.SC301,
Constants.MfcScannerOldSystem.SC302,
Constants.MfcScannerOldSystem.SC303,
Constants.MfcScannerOldSystem.SC304,
Constants.MfcScannerOldSystem.SC305,
Constants.MfcScannerOldSystem.SC306,
Constants.MfcScannerOldSystem.SC307,
Constants.MfcScannerOldSystem.SC308,
Constants.MfcScannerOldSystem.SC309,
Constants.MfcScannerOldSystem.SC310,
//MLS Aisles
Constants.MfcScannerOldSystem.SC401,
Constants.MfcScannerOldSystem.SC402,
Constants.MfcScannerOldSystem.SC403,
Constants.MfcScannerOldSystem.SC404,
Constants.MfcScannerOldSystem.SC405,
Constants.MfcScannerOldSystem.SC406,
Constants.MfcScannerOldSystem.SC407,
Constants.MfcScannerOldSystem.SC408,
Constants.MfcScannerOldSystem.SC409,
Constants.MfcScannerOldSystem.SC410,
Constants.MfcScannerOldSystem.SC411,
Constants.MfcScannerOldSystem.SC412,
//Constants.MfcScannerOldSystem.SC501,
//Constants.MfcScannerOldSystem.SC502,
//Constants.MfcScannerOldSystem.SC503,
//Constants.MfcScannerOldSystem.SC504,
//Constants.MfcScannerOldSystem.SC505,
//Constants.MfcScannerOldSystem.SC601,
//Constants.MfcScannerOldSystem.SC602,
//Constants.MfcScannerOldSystem.SC603,
//Constants.MfcScannerOldSystem.SC604,
//Constants.MfcScannerOldSystem.SC605
};
#region SendTord Methoden MFRtoSPS
/// <summary>
/// Transportorder to device PLCs
/// <para>Richtung : MFRtoSPS</para>
/// <para>pFillContent true</para>
/// <para>Wenn pFillContent True, wird Telegram mit dem Zeichen '0' linksbündig aufgefüllt.</para>
/// </summary>
/// <param name = "db">Ef Core Database Context</param>
/// <param name = "pHU"> (Lenght:8 PadChar:'*' L/R: L)</param>
/// <param name = "pSrc">Source (Lenght:11 PadChar:'-' L/R: R)</param>
/// <param name = "pDest">Destination (Lenght:11 PadChar:'-' L/R: R)</param>
/// <param name = "pType">LE type from DB (Lenght:10 PadChar:'0' L/R: R)</param>
/// <param name = "pToID">Orders Conveyor ID (Lenght:10 PadChar:'0' L/R: L)</param>
/// <returns></returns>
public static bool SendTordEtra(Gebhardt.StoreWare.Wcs.Common.DbAccess.IWcsDbContext db, string pHU, string pSrc, string pDest, string pType, string pToID)
{
bool sent = false;
if (pDest.Contains("EMB") || pSrc.Contains("EMB") || pSrc.Contains(Constants.MfcScanner.SC0505) || pSrc.Contains(Constants.MfcScanner.SC0137) || pSrc.Contains(Constants.MfcScanner.SC0183))
{
sent = SendTordWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_Dest: pDest, p_Type: pType, p_ToID: pToID,
p_DataDestination: ConveyorTelegramsDataDestination.D_PLC05);
}
else if (pDest.StartsWith("PIC1"))
{
sent = SendTordWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_Dest: pDest, p_Type: pType, p_ToID: pToID,
p_DataDestination: ConveyorTelegramsDataDestination.D_PLC03);
}
else if (pSrc.Contains(Constants.MfcAllDestinations.ERR12) || pSrc.StartsWith("SEQ") || pDest.StartsWith("PIC2"))
{
sent = SendTordWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_Dest: pDest, p_Type: pType, p_ToID: pToID,
p_DataDestination: ConveyorTelegramsDataDestination.D_PLC02);
}
// Old telegrams
// TODO: Fix to send to 01 for real system!
if (destinationsOldSystem.Contains(pSrc) || destinationsOldSystem.Contains(pDest))
{
var leType = db.LeType.Where(t => t.TypePrefix == pHU.Substring(0, 1)).FirstOrDefault();
int minWeight = 1000;
int maxWeight = 50000;
if (leType != null)
{
minWeight = leType.TareWeight - 1000;
maxWeight = leType.MaxWeight;
}
string destinationForOldSystem = pDest;
string[] replenishmentArea = new[]
{
Constants.MfcAllDestinationsOldSystem.IPT01,
Constants.MfcAllDestinationsOldSystem.IPT02,
Constants.MfcAllDestinationsOldSystem.IPT03,
Constants.MfcAllDestinationsOldSystem.ETXBU,
Constants.MfcAllDestinationsOldSystem.ETXBU_M,
Constants.MfcAllDestinationsOldSystem.ETXBU_S,
Constants.MfcScannerOldSystem.SC102,
Constants.MfcScannerOldSystem.SC101,
Constants.MfcScannerOldSystem.SC103,
Constants.MfcAllDestinationsOldSystem.TOPUP
};
if (!destinationsOldSystem.Contains(pDest))
{
destinationForOldSystem = Constants.MfcAllDestinationsOldSystem.QCS01;
}
if (replenishmentArea.Contains(pDest) || replenishmentArea.Contains(pSrc))
{
sent = Send0001WithDefaults(db, ConveyorTelegramsDataSource.S_31, ConveyorTelegramsDataDestination.D_02, pHU,
destinationForOldSystem, p_WgtMin: minWeight.ToString(), p_WgtMax: maxWeight.ToString());
}
sent = Send0001WithDefaults(db, ConveyorTelegramsDataSource.S_31, ConveyorTelegramsDataDestination.D_01, pHU,
destinationForOldSystem, p_WgtMin: minWeight.ToString(), p_WgtMax: maxWeight.ToString());
}
//Don't send to new system if both source and destination are in old system
if (!(destinationsOldSystem.Contains(pSrc) && destinationsOldSystem.Contains(pDest)))
{
//If only Destination is in old system, we need to tell the new system to drive to LOOP3, only this destination is known
if (destinationsOldSystem.Contains(pDest))
{
pDest = Constants.MfcAllDestinationsOldSystem.LOOP3;
}
sent = SendTordWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_Dest: pDest, p_Type: pType, p_ToID: pToID,
p_DataDestination: ConveyorTelegramsDataDestination.D_PLC01);
}
return sent;
}
public static bool SendTordPalletizingEtra(Gebhardt.StoreWare.Wcs.Common.DbAccess.IWcsDbContext db, string pHU, string pSrc, string pToID, List<string> pDestList)
{
//PLC needs the order to be ascending according to transport direction
pDestList = pDestList.OrderBy(d => d.Substring(d.Length - 1, 1)).ToList();
return SendTordWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC02, p_HU: pHU, p_Src: pSrc, p_ToID: pToID, p_DestList: $"[{string.Join(',', pDestList)}]", p_Dest: pDestList[0]);
}
public static bool SendTordPalletDispatchLineEtra(Gebhardt.StoreWare.Wcs.Common.DbAccess.IWcsDbContext db, string pHU, string pSrc, string pToID, string pDest, bool pWrap, bool pPrint)
{
return SendTordWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC07, p_HU: pHU, p_Src: pSrc, p_Dest: pDest, p_ToID: pToID, p_Wrap: pWrap ? "1" : "0", p_Print: pPrint ? "1" : "0");
}
#endregion SendTord Methoden MFRtoSPS
#region SendDeparture Methoden MFRtoSPS
/// <summary>
/// Departs immediately
/// <para>Richtung : MFRtoSPS</para>
/// <para>pFillContent true</para>
/// <para>Wenn pFillContent True, wird Telegram mit dem Zeichen '0' linksbündig aufgefüllt.</para>
/// </summary>
/// <param name = "db">Ef Core Database Context</param>
/// <param name = "pHU"> (Lenght:8 PadChar:'*' L/R: L)</param>
/// <param name = "pSrc"> (Lenght:11 PadChar:'-' L/R: R)</param>
/// <returns></returns>.
public static bool SendDepartureEtra(Gebhardt.StoreWare.Wcs.Common.DbAccess.IWcsDbContext db, string pHU, string pSrc)
{
if (pSrc.StartsWith("PIC1"))
{
return SendDepartureWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC03);
}
if (pSrc.StartsWith("PIC2") || pSrc.StartsWith(Common.Constants.MfcAllDestinations.ERR12))
{
return SendDepartureWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC02);
}
if (pSrc.StartsWith(Common.Constants.MfcAllDestinations.ERR11))
{
return SendDepartureWithDefaults(db, p_HU: pHU, p_Src: pSrc, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC05);
}
if (destinationsOldSystem.Contains(pSrc))
{
return Send0006WithDefaults(db, p_HU: pHU, p_Src: pSrc);
}
return SendDepartureWithDefaults(db, p_HU: pHU, p_Src: pSrc);
}
#endregion SendDepature Methoden MFRtoSPS
#region SendTordDelete Methoden MFRtoSPS
/// <summary>
/// Delete an order
/// <para>Richtung : MFRtoSPS</para>
/// <para>pFillContent true</para>
/// <para>Wenn pFillContent True, wird Telegram mit dem Zeichen '0' linksbündig aufgefüllt.</para>
/// </summary>
/// <param name = "db">Ef Core Database Context</param>
/// <param name = "pHU"> (Lenght:12 PadChar:'*' L/R: L)</param>
/// <param name = "pToID">Orders Conveyor ID (Lenght:10 PadChar:'0' L/R: L)</param>
/// <returns></returns>
public static bool SendTordDeleteEtra(Gebhardt.StoreWare.Wcs.Common.DbAccess.IWcsDbContext db, string pHU)
{
//Todo: only send Tords to PLCs involved in the transport?
SendTordDeleteWithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC01);
SendTordDeleteWithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC02);
SendTordDeleteWithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC03);
SendTordDeleteWithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC05);
//Old PLC
Send0002WithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_01);
Send0002WithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_02);
return SendTordDeleteWithDefaults(db, p_HU: pHU, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC07);
}
#endregion SendTordDelete Methoden MFRtoSPS
#region SendResetData Methoden MFRtoSPS
/// <summary>
///TelegramsContent hat Constant Parameters
/// Reset data
/// <para>Richtung : MFRtoSPS</para>
/// <para>pFillContent true</para>
/// <para>Wenn pFillContent True, wird Telegram mit dem Zeichen '0' linksbündich ausgefüllt.</para>
/// </summary>
/// <param name = "pDataDestination">Ziel</param>
/// <param name = "p_Info"> (Lenght:50 PadChar:'§' L/R: R)</param>
/// <returns></returns>
public static bool SendResetDataEtra(DbAccess.IWcsDbContext db, Enums.ResetDataInfos p_Info)
{
SendResetDataWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC01, p_Info: p_Info.GetEnumDescription());
SendResetDataWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC02, p_Info: p_Info.GetEnumDescription());
SendResetDataWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC03, p_Info: p_Info.GetEnumDescription());
SendResetDataWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC05, p_Info: p_Info.GetEnumDescription());
Send0004WithDefaults(db);
return SendResetDataWithDefaults(db, p_DataDestination: ConveyorTelegramsDataDestination.D_PLC07, p_Info: p_Info.GetEnumDescription());
}
#endregion SendResetData Methoden MFRtoSPS
}
#endregion
}

View File

@@ -1,35 +0,0 @@
public void SetLeRequestDeparture(string leNo, string placeName)
{
using var db = _dbContextFactory.GetDbContext();
var entry = db.Destination.GetByName(placeName);
NullCheck(entry, typeof(Destination), placeName);
if (!db.Le.Any(l => l.LeNo == leNo))
{
throw new InvalidOperationException($"LE {leNo} does not exist.");
}
// only check for regular LE; NoRead can be set in any entry
if (leNo != ConstantsCommon.ConstantsLE.NoRead)
{
db.Destination.GetByLeNo(leNo).ForEach(d =>
{
if (d.Name == placeName)
{
if (d.Status != DestinationStatus.LeArrived)
{
Log.Write(LogLevel.Error,
$"{nameof(Le)} {leNo} is set for {nameof(Destination)} {placeName}. But {nameof(Destination.Status)} is {d.Status}.");
}
}
else
{
Log.Write(LogLevel.Error,
$"{nameof(Le)} {leNo} is already set for {nameof(Destination)} {placeName}. {nameof(Destination.Status)} is {d.Status}. Will be deleted from {nameof(Destination)} entry");
d.SetLeDeparted();
}
});
}
db.Destination.GetByName(placeName)?.SetLeRequestDeparture(leNo);
db.SaveChanges();
}

View File

@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="Conn1" value="OrderManager" />
<add key="Conn2" value="ToEmptyLeBuffer" />
<add key="Conn3" value="LoopOverloadDistribution" />
<add key="Conn4" value="StartInitialOrdersHost" />
<add key="Intervall_OrderManager" value="345" />
<add key="Intervall_ToEmptyLeBuffer" value="345" />
<add key="Intervall_LoopOverloadDistribution" value="345" />
<add key="Intervall_StartInitialOrdersHost" value="345" />
</appSettings>
</configuration>

View File

@@ -1,66 +0,0 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common.Unity;
using Unity;
using Unity.Resolution;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
internal class Haupt
{
[STAThread]
private static void Main()
{
try
{
if (AppConfigVerifier.CheckAndWriteToLog())
{
ProcessManager manager = new(Convert.ToInt32(ConfigurationManager.AppSettings["ctrlTimer"]), true, ProcessClass.None, null);
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();
IUnityContainer unityContainer = WcsContainerFactory.GetInstance();
foreach (string connection in usedConnections)
{
string[] parts = connection.Split(':');
string className = parts[0];
switch (className)
{
case nameof(ToEmptyLeBuffer):
manager.RegisterWorker(unityContainer.Resolve<ToEmptyLeBuffer>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_ToEmptyLeBuffer"]))));
break;
case nameof(LoopOverloadDistribution):
// Do not use LoopOverloadDistribution for ETRA
break;
manager.RegisterWorker(unityContainer.Resolve<LoopOverloadDistribution>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_LoopOverloadDistribution"]))));
break;
case nameof(OrderManager):
manager.RegisterWorker(unityContainer.Resolve<OrderManager>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_OrderManager"]))));
break;
case nameof(StartInitialOrdersHost):
manager.RegisterWorker(unityContainer.Resolve<StartInitialOrdersHost>(new ParameterOverride("workInterval", Convert.ToInt32(ConfigurationManager.AppSettings["Intervall_StartInitialOrdersHost"]))));
break;
default:
throw new NotImplementedException(className);
}
}
manager.RunWorkers();
}
}
catch (Exception exception)
{
Log.WriteException(exception);
}
}
}
}

View File

@@ -1,330 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
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 static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
public class ToEmptyLeBuffer : ProcessWorker
{
private record AisleUtilization(AisleForLe Aisle, int Utilization);
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly IDestinationService _destinationService;
private readonly ILeService _leService;
private readonly ITransportOrderService _transportOrderService;
public ToEmptyLeBuffer(int workInterval,
IDestinationService destinationService,
ILeService leService,
ITransportOrderService transportOrderService,
IAisleService aisleService,
IWcsDbContextFactory dbContextFactory)
: base(nameof(ToEmptyLeBuffer), workInterval, true)
{
_destinationService = destinationService;
_leService = leService;
_transportOrderService = transportOrderService;
_dbContextFactory = dbContextFactory;
}
public override bool DoWork()
{
try
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
//Retrieve aisles suited for storage (i.e. lifter/handover aisles are excluded)
List<AisleForLe> availableAisles = db.Aisle.GetAislesWithStorageCompartmentsReadyForRetrieval();
availableAisles = FilterForAllowedAisles(availableAisles);
SupplyBuffer(LeTypeName.MiniloadSmall, Constants.MfcAllDestinations.EMB_S, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL01);
SupplyBuffer(LeTypeName.MiniloadBig, Constants.MfcAllDestinations.EMB_M, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL01);
SupplyBuffer(LeTypeName.MiniloadSmall, Constants.MfcAllDestinationsOldSystem.ETXBU_S, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL02);
SupplyBuffer(LeTypeName.MiniloadBig, Constants.MfcAllDestinationsOldSystem.ETXBU_M, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL02);
return true;
}
catch (Exception exception)
{
Log.WriteException(exception);
return false;
}
}
/// <summary>
/// Refills each buffer with empty LEs depending on its demand.
/// First, it is checked if empty LEs on conveyer can be rerouted.
/// Second, empty LE demand is satisfied by retrieval from storage.
/// Last, empty LEs that have been explicitly requested by the WMS are retrieved.
/// </summary>
/// <param name="leType">the type of le to use</param>
/// <param name="buffers">List of buffers to refill.</param>
/// <param name="availableAisles">
/// List of available aisles (aisle, output place, storage device in aisle (if any) are
/// ready)
/// </param>
private void SupplyBuffer(LeTypeName leType, string buffer, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
if (string.IsNullOrEmpty(buffer))
{
Log.Write(LogLevel.Error, $"No buffers provided for {nameof(SupplyBuffer)}");
return;
}
using IWcsDbContext db = _dbContextFactory.GetDbContext();
var destinationBuffer = db.ResourceSetting.GetResourceByName(buffer);
double destinationBufferDemand = destinationBuffer.Demand;
double destinationBufferCapacity = destinationBuffer.Capacity + destinationBuffer.Overload;
double bufferFreePercentage = destinationBufferDemand / destinationBufferCapacity * 100.0;
//Setting to minimize crane usage
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.EmptyBoxesDemandThreshold, out int demandThreshold, 40);
if (destinationBufferDemand > 0)
{
RerouteLesOnConveyor(leType, buffer);
}
//If the buffers free space percentage is greater than the threshold settings value we also take boxes from storage
if (bufferFreePercentage > demandThreshold)
{
RefillBuffersFromStorage(leType, buffer, availableAisles, preferredStorageArea);
}
RefillBuffersWithRequestedLes(buffer, availableAisles, preferredStorageArea);
}
/// <summary>
/// Refills buffers with demand by rerouting LEs on conveyor that have a storage area as destination
/// (i.e. aisle selection has not been performed yet.
/// </summary>
private void RerouteLesOnConveyor(LeTypeName leType, string buffers)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<string> destinationsAllowingForRerouting = _destinationService.Where(d => d.IsStorageArea).Select(d => d.Name).ToList();
List<string> dontAllowforReroutingLastWhere = new List<string>() { MfcAllDestinationsOldSystem.IPT01, MfcAllDestinationsOldSystem.IPT02, MfcAllDestinationsOldSystem.IPT03, MfcAllDestinationsOldSystem.TOPUP, MfcAllDestinationsOldSystem.REP01, MfcAllDestinationsOldSystem.REP02, MfcAllDestinationsOldSystem.REP03, MfcAllDestinationsOldSystem.ETXBU_M, MfcAllDestinationsOldSystem.ETXBU_S };
//Transport orders with a storage area as destination
OrderList orders = new(db.OrdersHost
.ByDestination(destinationsAllowingForRerouting)
.ByType(TransportOrderType.Transport)
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress)
.Where(o => o.Le.IsEmpty
&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone)
&& o.Le.Type == leType)
.AsEnumerable()
.Where(o => !o.Le.HasError() && !dontAllowforReroutingLastWhere.Contains(o.Le.LastWhere))
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.ToList());
foreach (OrderListItem order in orders)
{
Resource destination = db.ResourceSetting.ByName(buffers).GetResourceWithHighestDemand();
if (destination is { Demand: > 0 })
{
if (db.OrdersMiniload.ByLeNo(order.Le.LeNo).Open().Any())
{
continue;
}
//Reroute LE
Log.Write(LogLevel.Debug, $"LE {order.Le.LeNo} will be rerouted to {destination.Name}.");
_leService.CancelOpenTransports(order.Le.LeNo, $"Rerouted to {destination.Name}");
Log.Write(LogLevel.Debug, $"LE {order.Le.LeNo} will be rerouted to {destination.Name}.");
// This is to mitigate filling a very long string into the source field. Having AKL01 for the old system is not a problem.
string source;
if (order.Le.LastWhere == null || order.Le.LastWhere.StartsWith("["))
{
source = Constants.MfcAllDestinations.AKL01;
}
else
{
source = order.Le.LastWhere;
}
int id = _leService.CreateTransport(order.Le.LeNo, source, destination.Name);
_transportOrderService.StartNextTransport(id, nameof(RerouteLesOnConveyor));
}
else if (order.Status == TransportOrderStatus.Pending)
{
_transportOrderService.StartNextTransport(order.OrdersHostId, info: nameof(RerouteLesOnConveyor));
}
}
}
/// <summary>
/// Refills one or more buffers with empty LEs from the storage.
/// First, Overload for each buffer is checked and updated via the SettingsManager
/// Then, for each buffer with demand, an aisle is selected and a retrieval order is created.
/// </summary>
private void RefillBuffersFromStorage(LeTypeName leType, string buffers, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<Resource> buffersWithDemand = db.ResourceSetting.ByName(buffers).GetResources().WithDemand().OrderByDescending(r => r.Demand).ToList();
foreach (Resource buffer in buffersWithDemand)
{
if (buffer.Demand > 0)
{
AisleForLe aisle = GetAisleForEmptyLeRetrieval(leType, availableAisles, preferredStorageArea);
if (aisle != null)
{
//Refill buffer
//OrdersHost has no storage area, i.e. aisle name must be unique by itself when used as source.
Log.Write(LogLevel.Debug, $"Empty LE of type {leType} will be retrieved from {aisle.AisleName} and sent towards {buffer.Name}.");
int id = _leService.CreateTransport(LeType.GetNextEmptyLeTypeForActual(leType).ToString(), aisle.AisleName, buffer.Name);
_transportOrderService.StartNextTransport(id, nameof(RefillBuffersFromStorage));
}
}
}
}
/// <summary>
/// Refills buffers according to a request received from the WMS.
/// An initial <see cref="OrdersHost" /> has already been created when handling the request.
/// Here, select aisle, start <see cref="OrdersHost" /> and create <see cref="OrdersMiniload" />.
/// </summary>
private void RefillBuffersWithRequestedLes(string buffers, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
//Initial transport orders for empty LEs (requested by the WMS)
OrderList orders = new(db.OrdersHost
.ByDestination(buffers)
.ByStatus(TransportOrderStatus.Initial)
.ByType(TransportOrderType.Transport)
.OnlyNextEmpty()
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.ToList());
foreach (OrderListItem order in orders)
{
AisleForLe aisle = GetAisleForEmptyLeRetrieval(LeType.GetActualLeTypeForNextEmpty(order.Le.Type), availableAisles, preferredStorageArea);
if (aisle != null)
{
//OrdersHost has no storage area, i.e. aisle name must be unique by itself when used as source.
Log.Write(LogLevel.Debug, $"Requested empty LE of type {order.Le.Type} will be retrieved from {aisle.AisleName} and sent towards {order.Destination}.");
_transportOrderService.ScheduleOrdersHost(order.OrdersHostId, aisle.AisleName, nameof(RefillBuffersWithRequestedLes));
_transportOrderService.StartNextTransport(order.OrdersHostId, nameof(RefillBuffersWithRequestedLes));
}
}
}
private AisleForLe GetAisleForEmptyLeRetrieval(LeTypeName leTypeName, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<AisleForLe> availableForLeRetrieval = availableAisles.ToList();
//Left outer join empty LEs with open orders miniload, then exclude LEs with open orders.
var emptyLesInStorageWithoutOpenOrders = db.Le
.ByStatus(LeStatus.InStorage)
.Where(l => !l.Location.IsLocked)
.ByType(leTypeName)
.Empty()
.GroupJoin(db.OrdersHost.Open(),
le => le.LeNo,
o => o.LeNo,
(le, ordersHost) => new { Le = le, OrdersHost = ordersHost })
.SelectMany(j => j.OrdersHost.DefaultIfEmpty(),
(l, o) => new { l.Le, OrdersHost = o })
.Where(j => j.OrdersHost == null)
.ToList();
if (emptyLesInStorageWithoutOpenOrders.Count > 0)
{
Log.Write(LogLevel.Info, $"empty Les found: {emptyLesInStorageWithoutOpenOrders.Count}");
}
Dictionary<AisleForLe, int> aislesWithEmptyLes = emptyLesInStorageWithoutOpenOrders.GroupBy(xx => xx.Le.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count());
Dictionary<AisleForLe, int> aislesWithActiveNextEmptyMiniload = db.OrdersMiniload.Open().ByLeNo(LeType.GetNextEmptyLeTypeForActual(leTypeName).ToString()).ToList()
.GroupBy(xx => xx.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count());
//Leave only aisles containing LEs that can be retrieved
availableForLeRetrieval.RemoveAll(a => !aislesWithEmptyLes.ContainsKey(a) || (aislesWithEmptyLes.ContainsKey(a) && aislesWithActiveNextEmptyMiniload.ContainsKey(a) && aislesWithEmptyLes[a] - aislesWithActiveNextEmptyMiniload[a] <= 0));
// See if we can remove all other storage areas. If not, allow other.
if (availableForLeRetrieval.Any(xx => xx.StorageArea != preferredStorageArea)
&& availableAisles.Any(x => x.StorageArea == preferredStorageArea))
{
availableForLeRetrieval.RemoveAll(xx => xx.StorageArea != preferredStorageArea);
}
//Build one structre that has all information
Dictionary<AisleForLe, (int EmptyBoxes, int OrderedEmpties)> aislesWithNumbers = new Dictionary<AisleForLe, (int emptyBoxes, int orders)>();
foreach (var aisle in availableForLeRetrieval.Distinct())
{
int emptyBoxes = 0;
int orderedEmpties = 0;
if (aislesWithEmptyLes.ContainsKey(aisle))
{
emptyBoxes = aislesWithEmptyLes[aisle];
}
if (aislesWithActiveNextEmptyMiniload.ContainsKey(aisle))
{
orderedEmpties = aislesWithEmptyLes[aisle];
}
aislesWithNumbers.Add(aisle, (emptyBoxes, orderedEmpties));
}
AisleUtilization currentMinUtilization = null;
//Check MLS before Crane (as longterm MLS should be used for filled boxes), and we want more space in MLS
foreach (AisleForLe aisle in aislesWithNumbers.OrderBy(ar=>ar.Key.AisleName.StartsWith("C")).ThenByDescending(ar => ar.Value.EmptyBoxes - ar.Value.OrderedEmpties).Select(ar=>ar.Key))
{
AisleForLe inputAisle = db.AisleForLe.GetInputAisle(aisle);
int lesTowardsAisle = db.OrdersConveyor.Active().ByDestination(inputAisle.AisleName).Count();
//TODO include input/output le lifter orders?
//TODO include OrdersHost for orders that have no ordersMiniload yet?
int openOrdersForAisle = db.OrdersMiniload.Open().ByAisle(aisle.AisleName, aisle.StorageArea).Count();
int totalUtilization = openOrdersForAisle + lesTowardsAisle;
//We allow a utilization of 4 orders to be neglected, so we put more relevance on the number of totes
if (currentMinUtilization == null || currentMinUtilization.Utilization > totalUtilization + 4)
{
currentMinUtilization = new AisleUtilization(aisle, totalUtilization);
}
}
//Select aisle with lowest utilization
return currentMinUtilization?.Aisle;
}
/// <summary>
/// Filter depending on OnlyEmptiesToOldStorage setting:
/// If setting is true, get all empty LEs from old storage if available
/// </summary>
/// <param name="availableAisles"></param>
/// <param name="le"></param>
/// <returns></returns>
private List<AisleForLe> FilterForAllowedAisles(List<AisleForLe> availableAisles)
{
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.OnlyEmptiesToOldStorage, out bool onlyEmptiesToOldStorage, false, true);
if (!onlyEmptiesToOldStorage)
{
return availableAisles;
}
//get all empties from AKL02 if possible
if (availableAisles.Any(a => a.StorageArea == Constants.MfcAllDestinations.AKL02))
{
return availableAisles.Where(a => a.StorageArea == Constants.MfcAllDestinations.AKL02).ToList();
}
//Fallback get from available aisles
else
{
return availableAisles;
}
}
}
}

View File

@@ -1,45 +0,0 @@
public enum LeStatus
{
Created,
OnConveyor,
InStorage,
OnLhd,
/// <summary>
/// on an input place of a device to which the Le is not directly moved on by the conveyor
/// </summary>
OnInputPlace,
InDestinationZone,
/// <summary>
/// on an output place of a device from which the Le is not directly taken by the conveyor
/// </summary>
OnOutputPlace,
/// <summary>
/// When the LE leaves the system it is marked as deleted
/// </summary>
Deleted,
/// <summary>
/// see <see cref="ModelEnumParser.UnmappedEnumValue"/>
/// </summary>
Unmapped
}
public static class LeStatusGroups
{
public static readonly LeStatus[] Storage = new[]
{
LeStatus.InStorage,
LeStatus.OnLhd,
LeStatus.OnInputPlace,
LeStatus.OnOutputPlace
};
public static readonly LeStatus[] Conveyor = new[]
{
LeStatus.OnConveyor,
LeStatus.InDestinationZone
};
}

View File

@@ -1,16 +0,0 @@
public enum TransportOrderStatus
{
Initial,
Pending,
InProgress,
Transmitted,
InDestinationZone,
InSequencer,
Finished,
Cancelled,
/// <summary>
/// see <see cref="ModelEnumParser.UnmappedEnumValue"/>
/// </summary>
Unmapped
}

View File

@@ -1,42 +0,0 @@
public enum TransportOrderType
{
/// <summary>
/// Used for OrdersHost which is created by Wcs (empty le to buffer or le with no other order back to miniload)
/// </summary>
Transport,
/// <summary>
/// Used for OrdersHost which is created via a message from Wms
/// </summary>
TransportHost,
/// <summary>
/// Used for OrdersMiniload to store a le from input lane into shelf or drive thru from input lane to output lane
/// </summary>
Storage,
/// <summary>
/// used for OrdersMiniload to retrieve a le from shelf and put it to the output lane
/// </summary>
Retrieval,
/// <summary>
/// used for OrdersMiniload to restore a le from shelf to shelf
/// </summary>
Restorage,
/// <summary>
/// used for clearing a device or storage location.
/// </summary>
Clear,
/// <summary>
/// Datenabgleich
/// </summary>
DataSync,
/// <summary>
/// see <see cref="ModelEnumParser.UnmappedEnumValue"/>
/// </summary>
Unmapped
}

View File

@@ -1,30 +0,0 @@
<?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

@@ -1,45 +0,0 @@
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

@@ -1,132 +0,0 @@
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

@@ -1,44 +0,0 @@
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

@@ -1,203 +0,0 @@
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

@@ -1,90 +0,0 @@
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

@@ -1,224 +0,0 @@
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

@@ -1,17 +0,0 @@
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

@@ -1,25 +0,0 @@
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

@@ -1,111 +0,0 @@
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

@@ -1,94 +0,0 @@
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

@@ -1,136 +0,0 @@
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

@@ -1,219 +0,0 @@
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

@@ -1,51 +0,0 @@
//-----------------------------------------------------------------------------------------
// <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

@@ -1,33 +0,0 @@
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

@@ -1,28 +0,0 @@
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.
}
}
}

View File

@@ -1,25 +0,0 @@
/// <summary>
/// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources
/// </summary>
public void Finish()
{
OrdersConveyor.ToList().ForEach(oc =>
{
if (TransportOrderStatusGroups.Open.Contains(oc.Status))
{
oc.HandleFinished();
using IWcsDbContext db = new WcsDbContextFactory().GetDbContext();
ConveyorTelegrams.SendTordDeleteEtra(db, oc.LeNo);
db.SaveChanges();
}
});
OrdersMiniload.ToList().ForEach(om =>
{
if (TransportOrderStatusGroups.Open.Contains(om.Status))
{
om.HandleFinished();
}
});
Status = TransportOrderStatus.Finished;
UpdateResources(TransportOrderStatus.Finished, null);
}

View File

@@ -1,5 +0,0 @@
public OrdersHost ForceSetStatusInProgress()
{
Status = TransportOrderStatus.InProgress;
return this;
}

View File

@@ -1,9 +0,0 @@
public void UpdateResources(TransportOrderStatus resourceListStatus, List<ResourceList> occupiedBySameLeButDifferentOrder)
{
//If a OrdersHost is Finished or Cancelled all ResourceList for this OH are affected, for other status only entries with same Destination
Resources
.Where(r => !TransportOrderStatusGroups.Complete.Contains(r.Status) && (r.Name == Destination || resourceListStatus == TransportOrderStatus.Finished || resourceListStatus == TransportOrderStatus.Cancelled))
.ToList()
.ForEach(r => r.Status = resourceListStatus);
occupiedBySameLeButDifferentOrder?.ForEach(r => r.Status = TransportOrderStatus.Cancelled);
}

View File

@@ -1,25 +0,0 @@
public static IOrderedQueryable<OrdersHost> ApplyWmsOrderingSequencerRetrievalTime(this IQueryable<OrdersHost> entity,
IWcsDbContext db)
{
var query = entity
.GroupJoin(
db.OrdersHost
.GroupBy(xx => xx.IdOrderWmsHead)
.Select(g => new
{
IdOrderWmsHead = g.Key,
SequencerRetrievalTime = g.Min(y => y.SequencerRetrievalTime)
}),
o => o.IdOrderWmsHead,
m => m.IdOrderWmsHead,
(o, m) => new { o, m }
)
.SelectMany(x => x.m.DefaultIfEmpty(), (x, m) => new { x.o, m })
.OrderByDescending(x => x.o.IsStolen)
.ThenByDescending(x => x.o.IsDirectPicking)
.ThenByDescending(x => x.m.SequencerRetrievalTime != null)
.ThenBy(x => x.m.SequencerRetrievalTime)
.ThenBy(x => x.o.Created)
.Select(x => x.o);
return (IOrderedQueryable<OrdersHost>) query;
}

View File

@@ -1,4 +0,0 @@
public static IQueryable<OrdersHost> ByCancelledSequencerOrder(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => o.Source.Contains(WcsNames.SEQ) && o.Destination == MfcAllDestinations.StorageLoop2);
}

View File

@@ -1,4 +0,0 @@
public static IQueryable<OrdersHost> ExcludeNextEmpty(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => o.LeNo != LeTypeName.NextEmptyMiniloadSmall.ToString() && o.LeNo != LeTypeName.NextEmptyMiniloadBig.ToString());
}

View File

@@ -1,66 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
internal record OrderListItem(int OrdersHostId, TransportOrderStatus Status, Le Le, string Destination, int Priority, int? IdOrderWmsHead, DateTime Created, string HostDestination);
internal class OrderList : List<OrderListItem>
{
public OrderList(List<OrderListItem> items) : base(items)
{
}
/// <summary>
/// Removes all order list items with the same LeNo and a higher list index.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualLeNo(OrderListItem item)
{
if (item is {Le: { }})
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo);
}
}
/// <summary>
/// Removes all order list items with the same destination and a higher list index.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualDestination(OrderListItem item)
{
if (item != null)
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Destination == item.Destination);
}
}
/// <summary>
/// Removes all order list items with the same aisle name / storage area and a higher list index.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualAisle(OrderListItem item)
{
if (item?.Le?.StorageArea != null && item?.Le?.AisleName != null)
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.StorageArea == item.Le.StorageArea && i.Le.AisleName == item.Le.AisleName);
}
}
/// <summary>
/// Removes all order list items with a higher list index that have the same LeNo but lower priority.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualLeNoButLowerPriority(OrderListItem item)
{
if (item is {Le: { }})
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo && i.Priority < item.Priority);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
internal record OrderListItem(int OrdersHostId, TransportOrderStatus Status, Le Le, string Destination, int Priority, int? IdOrderWmsHead,
DateTime Created, string HostDestination, bool? DepartureFlag, string? DepartureLocation);
internal class OrderList : List<OrderListItem>
{
public OrderList(List<OrderListItem> items) : base(items)
{
}
/// <summary>
/// Removes all order list items with the same LeNo and a higher list index.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualLeNo(OrderListItem item)
{
if (item is {Le: { }})
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo);
}
}
/// <summary>
/// Removes all order list items with the same destination and a higher list index.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualDestination(OrderListItem item)
{
if (item != null)
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Destination == item.Destination);
}
}
/// <summary>
/// Removes all order list items with the same aisle name / storage area and a higher list index.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualAisle(OrderListItem item)
{
if (item?.Le?.StorageArea != null && item?.Le?.AisleName != null)
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.StorageArea == item.Le.StorageArea && i.Le.AisleName == item.Le.AisleName);
}
}
/// <summary>
/// Removes all order list items with a higher list index that have the same LeNo but lower priority.
/// </summary>
/// <param name="item"></param>
public void RemoveSubsequentWithEqualLeNoButLowerPriority(OrderListItem item)
{
if (item is {Le: { }})
{
RemoveAll(i => IndexOf(i) > IndexOf(item) && i.Le.LeNo == item.Le.LeNo && i.Priority < item.Priority);
}
}
}
}

View File

@@ -1,237 +1,276 @@
using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo;
public class OrderManager : ProcessWorker
{
private readonly IAisleService _aisleService;
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly IDestinationService _destinationService;
private readonly ILeService _leService;
private readonly ITransportOrderService _transportOrderService;
public OrderManager(IDestinationService destinationService, ITransportOrderService transportOrderService, ILeService leService, IAisleService aisleService, IWcsDbContextFactory dbContextFactory, int workInterval)
: base(nameof(OrderManager), workInterval, true)
{
_destinationService = destinationService;
_transportOrderService = transportOrderService;
_leService = leService;
_aisleService = aisleService;
_dbContextFactory = dbContextFactory;
}
public override bool DoWork()
{
bool workDone = false;
try
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
IQueryable<OrdersHost> pendingOrders = db.OrdersHost.ByStatus(TransportOrderStatus.Pending);
OrderList onConveyor = new(pendingOrders
.Where(o => o.Le.LocationId == null
&& (!o.Le.IsEmpty || o.Type == TransportOrderType.TransportHost)
&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone))
.OrderBy(o => o.StartTime)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(onConveyor);
var pendingOrdersWithDemand = pendingOrders
.Where(x => x.Le.Status != LeStatus.Created)
//Do not start pending orders for LEs inside a sequencer
.ExcludeOrdersInSequencer()
//Consider all destinations that are commissioning workstations or a storage area
.ByDestination(_destinationService.GetCommissioningWorkstations()
.Union(_destinationService.Where(d => d.IsStorageArea).Select(d => d.Name))
.Union(_destinationService.Where(d => d.IsSequencer).Select(d => d.Name)));
//// Always prioritize cancelled sequencer orders
//var cancelledSeqOrders = pendingOrdersWithDemand.ByCancelledSequencerOrder();
//OrderList forResourcesWithDemand = new(cancelledSeqOrders
// .Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
// .AsNoTracking()
// .ToList());
//workDone |= StartNextOrders(forResourcesWithDemand);
//Then do the normal orders with demand.
pendingOrdersWithDemand = pendingOrdersWithDemand.ApplyWmsOrderingSequencerRetrievalTime(db);
OrderList forResourcesWithDemand = new(pendingOrdersWithDemand
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(forResourcesWithDemand);
OrderList forOtherDestinations = new(pendingOrders
.Where(x => x.Le.Status != LeStatus.Created)
.ExcludeDestination(db.ResourceSetting.Select(r => r.Name).ToList())
.ApplyWmsOrderingSequencerRetrievalTime(db)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(forOtherDestinations);
}
catch (Exception ex)
{
Log.WriteException(ex);
}
return workDone;
}
private bool StartNextOrders(OrderList orders)
{
bool workDone = false;
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<Le> les = db.Le.Where(l => orders.Select(o => o.Le.LeNo).Contains(l.LeNo)).Distinct().ToList();
for (int i = 0; i < orders.Count; i++)
{
OrderListItem order = orders[i];
try
{
Le le = les.Single(l => l.LeNo == order.Le.LeNo);
if (LeIsExcludedAsAisleNotReady(orders, le, order)
|| LeIsExcludedAsOrderMiniloadIsActive(orders, db, le, order)
|| LeIsExcludedAsLeIsOnItsWayToNOK(orders, db, order))
{
workDone = true;
continue;
}
// Special case for sequencer orders: reserve half the space for each workstation
if (order.Destination.StartsWith("SEQ"))
{
Resource sequencerResource = db.ResourceSetting.GetResourceByName(order.Destination);
if (sequencerResource != null)
{
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaximumUsableCapacityPercentPerWorkstation, out int maximumUsableCapacityPercentPerWorkstation, 50, true);
maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation > 100 ? 100 : maximumUsableCapacityPercentPerWorkstation;
maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation < 50 ? 50 : maximumUsableCapacityPercentPerWorkstation;
int maximumUsableCapacityPerWorkstation = (sequencerResource.Capacity + sequencerResource.Overload) * maximumUsableCapacityPercentPerWorkstation / 100;
// Count active orders for this specific HostDestination going to the same sequencer
int activeOrdersForHostDestination = db.OrdersHost
.Count(o => o.HostDestination == order.HostDestination
&& o.Destination == order.Destination
&& (o.Status == TransportOrderStatus.InProgress
|| o.Status == TransportOrderStatus.InDestinationZone
|| o.Status == TransportOrderStatus.InSequencer));
if (activeOrdersForHostDestination >= maximumUsableCapacityPerWorkstation)
{
Log.Write(LogLevel.Info, 30, $"Sequencer capacity limit reached for {order.HostDestination} at {order.Destination}. Active: {activeOrdersForHostDestination}, Reserved: {maximumUsableCapacityPerWorkstation}");
continue;
}
}
}
Resource resource = db.ResourceSetting.GetResourceByName(order.Destination);
if (resource is null or { Demand: > 0 })
{
// Only cancel active transports not transportHost. Only WMS is allowed to finish this orders
var activeOrdersNotOnTheWayToWorkstation = db.OrdersHost
.ByLeNo(order.Le.LeNo)
.Active()
.Where(o => o.Type != TransportOrderType.TransportHost);
if (activeOrdersNotOnTheWayToWorkstation.Any())
{
_leService.CancelActiveTransports(order.Le.LeNo, $"Another active order to a commissioning area exists");
} else if (!db.OrdersHost.ByLeNo(order.Le.LeNo).Active().Any())
{
_transportOrderService.StartNextTransport(order.OrdersHostId);
}
orders.RemoveSubsequentWithEqualLeNo(order);
workDone = true;
}
else if (resource is { Demand: <= 0 })
{
if (!le.IsInStorage())
{
List<string> destinationAisles = _destinationService.Where(d => d.IsStorage).Select(d => d.Name).ToList();
if (!db.OrdersHost.OpenByLeNo(order.Le.LeNo).ByDestination(destinationAisles).Any())
{
_transportOrderService.PostponeOrdersHost(order.OrdersHostId, $"Destination {order.Destination} has no demand.");
orders.RemoveSubsequentWithEqualLeNo(order);
}
continue;
}
orders.RemoveSubsequentWithEqualDestination(order);
}
}
catch (Exception ex)
{
Log.Write(LogLevel.Error, $"Can not start OrdersHost: {order.OrdersHostId} for LE: {order.Le.LeNo}");
Log.WriteException(ex);
}
}
return workDone;
}
private static bool LeIsExcludedAsOrderMiniloadIsActive(OrderList orders, IWcsDbContext db, Le le, OrderListItem order)
{
if (db.OrdersMiniload.Underway().ByLeNo(le.LeNo).Any())
{
orders.RemoveSubsequentWithEqualLeNo(order);
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started due to an open OrdersMiniload.");
return true;
}
return false;
}
private bool LeIsExcludedAsAisleNotReady(OrderList orders, Le le, OrderListItem order)
{
if (le.IsInStorage())
{
if (!_aisleService.IsAisleReadyForRetrievalOrder(order.Le.StorageArea, order.Le.AisleName))
{
orders.RemoveSubsequentWithEqualAisle(order);
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started as not all participating aisles / devices are available for {le.AisleName}/{le.StorageArea}.");
return true;
}
}
return false;
}
private bool LeIsExcludedAsLeIsOnItsWayToNOK(OrderList orders, IWcsDbContext db, OrderListItem order)
{
OrdersHost orderForSameLe = db.OrdersHost
.ByLeNo(order.Le.LeNo)
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone)
.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
.FirstOrDefault();
if (orderForSameLe == null)
{
return false;
}
if (orderForSameLe.Le.HasError() && orderForSameLe.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01)
|| order.Le.HasError() && order.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01))
{
orders.RemoveSubsequentWithEqualLeNo(order);
return true;
}
return false;
}
using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
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 Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo;
public class OrderManager : ProcessWorker
{
private readonly IAisleService _aisleService;
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly IDestinationService _destinationService;
private readonly ILeService _leService;
private readonly ITransportOrderService _transportOrderService;
public OrderManager(IDestinationService destinationService, ITransportOrderService transportOrderService, ILeService leService, IAisleService aisleService, IWcsDbContextFactory dbContextFactory, int workInterval)
: base(nameof(OrderManager), workInterval, true)
{
_destinationService = destinationService;
_transportOrderService = transportOrderService;
_leService = leService;
_aisleService = aisleService;
_dbContextFactory = dbContextFactory;
}
public override bool DoWork()
{
bool workDone = false;
try
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
IQueryable<OrdersHost> departureNotificationOrders = db.OrdersHost
.ByStatus(TransportOrderStatus.InDestinationZone, TransportOrderStatus.Pending)
.IsDepartureReady();
foreach (OrdersHost order in departureNotificationOrders)
{
// Restart order: Case when order has status InDestinationZone and has been flagged for departure by HostBooking (DepartureNotificationHandler)
if (order.Status == TransportOrderStatus.InDestinationZone)
{
order.UpdateResources(TransportOrderStatus.InProgress, null);
order.ForceSetStatusInProgress();
order.Le.SetStatus(LeStatus.OnConveyor);
_destinationService.SetLeRequestDeparture(order.Le.LeNo, order.DepartureLocation);
// TODO Check if telegram can be sent from here
ConveyorTelegrams.SendDepartureEtra(db, order.LeNo, order.DepartureLocation);
order.DepartureFlag = false;
order.DepartureLocation = null;
db.SaveChanges();
}
}
// Starting orders that are initial and were marked for departure by HostBooking (DepartureNotificationHandler)
OrderList departureOrders = new(departureNotificationOrders
.ByStatus(TransportOrderStatus.Pending)
.OrderBy(o => o.StartTime)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority,
o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(departureOrders);
IQueryable<OrdersHost> pendingOrders = db.OrdersHost.ByStatus(TransportOrderStatus.Pending);
OrderList onConveyor = new(pendingOrders
.Where(o => o.Le.LocationId == null
&& (!o.Le.IsEmpty || o.Type == TransportOrderType.TransportHost)
&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone))
.OrderBy(o => o.StartTime)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(onConveyor);
var pendingOrdersWithDemand = pendingOrders
.Where(x => x.Le.Status != LeStatus.Created)
//Do not start pending orders for LEs inside a sequencer
.ExcludeOrdersInSequencer()
//Consider all destinations that are commissioning workstations or a storage area
.ByDestination(_destinationService.GetCommissioningWorkstations()
.Union(_destinationService.Where(d => d.IsStorageArea).Select(d => d.Name))
.Union(_destinationService.Where(d => d.IsSequencer).Select(d => d.Name)));
//// Always prioritize cancelled sequencer orders
//var cancelledSeqOrders = pendingOrdersWithDemand.ByCancelledSequencerOrder();
//OrderList forResourcesWithDemand = new(cancelledSeqOrders
// .Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
// .AsNoTracking()
// .ToList());
//workDone |= StartNextOrders(forResourcesWithDemand);
//Then do the normal orders with demand.
pendingOrdersWithDemand = pendingOrdersWithDemand.ApplyWmsOrderingSequencerRetrievalTime(db);
OrderList forResourcesWithDemand = new(pendingOrdersWithDemand
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(forResourcesWithDemand);
OrderList forOtherDestinations = new(pendingOrders
.Where(x => x.Le.Status != LeStatus.Created)
.ExcludeDestination(db.ResourceSetting.Select(r => r.Name).ToList())
.ApplyWmsOrderingSequencerRetrievalTime(db)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination , o.DepartureFlag, o.DepartureLocation))
.AsNoTracking()
.ToList());
workDone |= StartNextOrders(forOtherDestinations);
}
catch (Exception ex)
{
Log.WriteException(ex);
}
return workDone;
}
private bool StartNextOrders(OrderList orders)
{
bool workDone = false;
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<Le> les = db.Le.Where(l => orders.Select(o => o.Le.LeNo).Contains(l.LeNo)).Distinct().ToList();
for (int i = 0; i < orders.Count; i++)
{
OrderListItem order = orders[i];
try
{
Le le = les.Single(l => l.LeNo == order.Le.LeNo);
if (LeIsExcludedAsAisleNotReady(orders, le, order)
|| LeIsExcludedAsOrderMiniloadIsActive(orders, db, le, order)
|| LeIsExcludedAsLeIsOnItsWayToNOK(orders, db, order))
{
workDone = true;
continue;
}
// Special case for sequencer orders: reserve half the space for each workstation
if (order.Destination.StartsWith("SEQ"))
{
Resource sequencerResource = db.ResourceSetting.GetResourceByName(order.Destination);
if (sequencerResource != null)
{
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaximumUsableCapacityPercentPerWorkstation, out int maximumUsableCapacityPercentPerWorkstation, 50, true);
maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation > 100 ? 100 : maximumUsableCapacityPercentPerWorkstation;
maximumUsableCapacityPercentPerWorkstation = maximumUsableCapacityPercentPerWorkstation < 50 ? 50 : maximumUsableCapacityPercentPerWorkstation;
int maximumUsableCapacityPerWorkstation = (sequencerResource.Capacity + sequencerResource.Overload) * maximumUsableCapacityPercentPerWorkstation / 100;
// Count active orders for this specific HostDestination going to the same sequencer
int activeOrdersForHostDestination = db.OrdersHost
.Count(o => o.HostDestination == order.HostDestination
&& o.Destination == order.Destination
&& (o.Status == TransportOrderStatus.InProgress
|| o.Status == TransportOrderStatus.InDestinationZone
|| o.Status == TransportOrderStatus.InSequencer));
if (activeOrdersForHostDestination >= maximumUsableCapacityPerWorkstation)
{
Log.Write(LogLevel.Info, 30, $"Sequencer capacity limit reached for {order.HostDestination} at {order.Destination}. Active: {activeOrdersForHostDestination}, Reserved: {maximumUsableCapacityPerWorkstation}");
continue;
}
}
}
Resource resource = db.ResourceSetting.GetResourceByName(order.Destination);
if (resource is null or { Demand: > 0 })
{
// Only cancel active transports not transportHost. Only WMS is allowed to finish this orders
var activeOrdersNotOnTheWayToWorkstation = db.OrdersHost
.ByLeNo(order.Le.LeNo)
.Active()
.Where(o => o.Type != TransportOrderType.TransportHost);
if (activeOrdersNotOnTheWayToWorkstation.Any())
{
_leService.CancelActiveTransports(order.Le.LeNo, $"Another active order to a commissioning area exists");
} else if (!db.OrdersHost.ByLeNo(order.Le.LeNo).Active().Any())
{
_transportOrderService.StartNextTransport(order.OrdersHostId);
// Should not change through Transportstart (flag is set to false after OrdersHost start) because boolean is saved inside OrderListItem
if (order.DepartureFlag == true)
{
// Centralized from HostBooking (DepartureNotificationHandler) for otherPicOrder
_destinationService.SetLeRequestDeparture(order.Le.LeNo, order.DepartureLocation);
ConveyorTelegrams.SendDepartureEtra(db, order.Le.LeNo, order.DepartureLocation);
db.Le.FirstOrDefault(l => l.LeNo == order.Le.LeNo)?.SetStatus(LeStatus.OnConveyor);
db.SaveChanges();
}
}
orders.RemoveSubsequentWithEqualLeNo(order);
workDone = true;
}
else if (resource is { Demand: <= 0 })
{
if (!le.IsInStorage())
{
List<string> destinationAisles = _destinationService.Where(d => d.IsStorage).Select(d => d.Name).ToList();
if (!db.OrdersHost.OpenByLeNo(order.Le.LeNo).ByDestination(destinationAisles).Any())
{
_transportOrderService.PostponeOrdersHost(order.OrdersHostId, $"Destination {order.Destination} has no demand.");
orders.RemoveSubsequentWithEqualLeNo(order);
}
continue;
}
orders.RemoveSubsequentWithEqualDestination(order);
}
}
catch (Exception ex)
{
Log.Write(LogLevel.Error, $"Can not start OrdersHost: {order.OrdersHostId} for LE: {order.Le.LeNo}");
Log.WriteException(ex);
}
}
return workDone;
}
private static bool LeIsExcludedAsOrderMiniloadIsActive(OrderList orders, IWcsDbContext db, Le le, OrderListItem order)
{
if (db.OrdersMiniload.Underway().ByLeNo(le.LeNo).Any())
{
orders.RemoveSubsequentWithEqualLeNo(order);
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started due to an open OrdersMiniload.");
return true;
}
return false;
}
private bool LeIsExcludedAsAisleNotReady(OrderList orders, Le le, OrderListItem order)
{
if (le.IsInStorage())
{
if (!_aisleService.IsAisleReadyForRetrievalOrder(order.Le.StorageArea, order.Le.AisleName))
{
orders.RemoveSubsequentWithEqualAisle(order);
Log.Write(LogLevel.Info, $"{nameof(OrdersHost)} with Id '{order.OrdersHostId}' for LE {le.LeNo} cannot be started as not all participating aisles / devices are available for {le.AisleName}/{le.StorageArea}.");
return true;
}
}
return false;
}
private bool LeIsExcludedAsLeIsOnItsWayToNOK(OrderList orders, IWcsDbContext db, OrderListItem order)
{
OrdersHost orderForSameLe = db.OrdersHost
.ByLeNo(order.Le.LeNo)
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone)
.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
.FirstOrDefault();
if (orderForSameLe == null)
{
return false;
}
if (orderForSameLe.Le.HasError() && orderForSameLe.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01)
|| order.Le.HasError() && order.Destination.IsInList(MfcAllDestinations.ERR12, MfcAllDestinations.ERR11, MfcAllDestinationsOldSystem.ERR01))
{
orders.RemoveSubsequentWithEqualLeNo(order);
return true;
}
return false;
}
}

View File

@@ -1,205 +1,225 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.Shared.DbAccess;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
using Gebhardt.StoreWare.Wcs.Common.Dao.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 Microsoft.EntityFrameworkCore;
using static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
public class StartInitialOrdersHost : ProcessWorker
{
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly ITransportOrderService _transportOrderService;
public StartInitialOrdersHost(int workInterval, ITransportOrderService transportOrderService, IWcsDbContextFactory dbContextFactory) : base(nameof(StartInitialOrdersHost), workInterval, true)
{
_transportOrderService = transportOrderService;
_dbContextFactory = dbContextFactory;
}
public override bool DoWork()
{
try
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
OrderList initialOrders = new(db.OrdersHost
.ByStatus(TransportOrderStatus.Initial)
.ExcludeNextEmpty()
.Where(o => o.Le.Status != LeStatus.Created)
.ApplyWmsOrderingSequencerRetrievalTime(db)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.ToList());
//This step is needed to only start orders with all LEs available
initialOrders = OnlyFirstOrderPerLeNo(db, initialOrders);
return StartOrders(initialOrders);
}
catch (Exception ex)
{
Log.WriteException(ex);
return false;
}
}
/// <summary>
/// Returns only the first order per LE
/// </summary>
/// <param name="initialOrders"></param>
/// <returns>Filtered List</returns>
private OrderList OnlyFirstOrderPerLeNo(IWcsDbContext db, OrderList initialOrders)
{
List<OrderListItem> distinctOrderList = new ();
List<OrderListItem> lockedOrders = new();
foreach (OrderListItem order in initialOrders)
{
//Only take first order per LE
if (!distinctOrderList.Any(d=>d.Le.LeNo == order.Le.LeNo))
{
distinctOrderList.Add(order);
}
//Others are marked as locked (for info message)
else
{
lockedOrders.Add(order);
Log.Write(LogLevel.Info, 60, $"OrdersHost ID {order.OrdersHostId} for LeNo {order.Le.LeNo} waits for OrdersHost: ID {distinctOrderList.Where(d => d.Le.LeNo == order.Le.LeNo).First().OrdersHostId} which is started first.");
}
}
//Make visible in DB (and to user)
string infoMessage = "Le has transports that are started first.";
var markOrders = db.OrdersHost.Where(o => lockedOrders.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info != infoMessage).ToList();
foreach (var order in markOrders)
{
order.Info = infoMessage;
}
//Rest Info if order can be started
var unMarkOrders = db.OrdersHost.Where(o => distinctOrderList.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info == infoMessage).ToList();
foreach (var order in unMarkOrders)
{
order.Info = null;
}
db.SaveChanges();
return new OrderList(distinctOrderList);
}
private bool StartOrders(OrderList orderList)
{
bool workDone = false;
using IWcsDbContext db = _dbContextFactory.GetDbContext();
for (int i = 0; i < orderList.Count; i++)
{
OrderListItem orderToBeScheduled = orderList[i];
try
{
// If there exists a cancelled sequencer orders for this LE this should always be started first
var cancelledSequencerOrder = db.OrdersHost
.ByLeNo(orderToBeScheduled.Le.LeNo)
.ByStatus(TransportOrderStatus.Initial)
.ByCancelledSequencerOrder()
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination))
.ToList();
if (cancelledSequencerOrder.FirstOrDefault() != null)
{
var cancelledOrderToBeScheduled = cancelledSequencerOrder.FirstOrDefault();
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {cancelledOrderToBeScheduled.OrdersHostId} will be scheduled since this is a cancelled sequencer order.");
_transportOrderService.ScheduleOrdersHost(cancelledOrderToBeScheduled.OrdersHostId);
//Order has been scheduled. Do not consider orders for the same LE.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
workDone = true;
continue;
}
OrdersHost orderForSameLe = db.OrdersHost
.ByLeNo(orderToBeScheduled.Le.LeNo)
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone, TransportOrderStatus.InSequencer)
.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
.FirstOrDefault();
if (orderForSameLe != null && orderForSameLe.Id != orderToBeScheduled.OrdersHostId)
{
workDone = TryScheduleWithExistingOrder(orderForSameLe, db, orderToBeScheduled);
//Check equal LEs with open order only once
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
continue;
}
if (TryScheduleWithoutExistingOrder(orderToBeScheduled, db))
{
//Order has been scheduled. Do not consider orders for the same LE.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
workDone = true;
continue;
}
else
{
Log.Write(LogLevel.Debug, $"Destination: {orderToBeScheduled.Destination} has no ressources, skip all orders to this destination");
orderList.RemoveSubsequentWithEqualDestination(orderToBeScheduled);
}
}
catch(Exception ex)
{
Log.Write(LogLevel.Error, $"Can not set ordersHost {orderToBeScheduled.OrdersHostId} for LE {orderToBeScheduled.Le.LeNo} to pending. Due to Exception:");
Log.WriteException(ex);
}
//We can remove all subsequent orders with same LE, because the list is already ordered in a way that priorities are taken into account.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
}
return workDone;
}
private bool TryScheduleWithoutExistingOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
{
if (CanScheduleOrder(orderToBeScheduled, db))
{
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {orderToBeScheduled.OrdersHostId} will be scheduled, as there are no other {nameof(OrdersHost)} scheduled or active orders for the same LE.");
_transportOrderService.ScheduleOrdersHost(orderToBeScheduled.OrdersHostId);
return true;
}
return false;
}
private bool CanScheduleOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
{
// Orders with their associated LE not being in storage can always be scheduled...
//TODO Check for OnConveyor/InDestinationZone? IsInStorage() does not cover OnInputLeLifter/OnLhd/OnOutputLeLifter...
if (!orderToBeScheduled.Le.IsInStorage())
{
return true;
}
//...if LE is in storage, whether the order can be scheduled is determined by the amount of pending orders that may exist towards the destination and the destination's demand.
if (!SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaxPendingOrdersPerDestination, out int maxPendingOrdersPerDestination))
{
maxPendingOrdersPerDestination = 10;
}
Resource resourceDestination = db.ResourceSetting.GetResourceByName(orderToBeScheduled.Destination);
// for unmanaged resources, a demand of 1 is used.
int destinationDemand = resourceDestination?.Demand ?? 1;
int countPendingOrdersToDestination = db.OrdersHost.ByStatus(TransportOrderStatus.Pending).ByDestination(orderToBeScheduled.Destination).Count();
return destinationDemand - countPendingOrdersToDestination > 0 || countPendingOrdersToDestination - destinationDemand < maxPendingOrdersPerDestination;
}
private bool TryScheduleWithExistingOrder(OrdersHost orderForSameLe, IWcsDbContext db, OrderListItem orderToBeScheduled)
{
// We not want to overwrite existing orders. We wait until we are back in Storage. (This happened only about 40 times a day.
return false;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.Shared.DbAccess;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
using Gebhardt.StoreWare.Wcs.Common.Dao.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 Microsoft.EntityFrameworkCore;
using static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
public class StartInitialOrdersHost : ProcessWorker
{
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly ITransportOrderService _transportOrderService;
public StartInitialOrdersHost(int workInterval, ITransportOrderService transportOrderService, IWcsDbContextFactory dbContextFactory) : base(nameof(StartInitialOrdersHost), workInterval, true)
{
_transportOrderService = transportOrderService;
_dbContextFactory = dbContextFactory;
}
public override bool DoWork()
{
try
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
OrderList initialOrders = new(db.OrdersHost
.ByStatus(TransportOrderStatus.Initial)
.ExcludeNextEmpty()
.Where(o => o.Le.Status != LeStatus.Created)
.ApplyWmsOrderingSequencerRetrievalTime(db)
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
.ToList());
//This step is needed to only start orders with all LEs available
initialOrders = OnlyFirstOrderPerLeNo(db, initialOrders);
return StartOrders(initialOrders);
}
catch (Exception ex)
{
Log.WriteException(ex);
return false;
}
}
/// <summary>
/// Returns only the first order per LE
/// </summary>
/// <param name="initialOrders"></param>
/// <returns>Filtered List</returns>
private OrderList OnlyFirstOrderPerLeNo(IWcsDbContext db, OrderList initialOrders)
{
List<OrderListItem> distinctOrderList = new ();
List<OrderListItem> lockedOrders = new();
foreach (OrderListItem order in initialOrders)
{
//Only take first order per LE
if (!distinctOrderList.Any(d=>d.Le.LeNo == order.Le.LeNo))
{
distinctOrderList.Add(order);
}
//Others are marked as locked (for info message)
else
{
lockedOrders.Add(order);
Log.Write(LogLevel.Info, 60, $"OrdersHost ID {order.OrdersHostId} for LeNo {order.Le.LeNo} waits for OrdersHost: ID {distinctOrderList.Where(d => d.Le.LeNo == order.Le.LeNo).First().OrdersHostId} which is started first.");
}
}
//Make visible in DB (and to user)
string infoMessage = "Le has transports that are started first.";
var markOrders = db.OrdersHost.Where(o => lockedOrders.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info != infoMessage).ToList();
foreach (var order in markOrders)
{
order.Info = infoMessage;
}
//Rest Info if order can be started
var unMarkOrders = db.OrdersHost.Where(o => distinctOrderList.Select(l => l.OrdersHostId).Contains(o.Id) && o.Info == infoMessage).ToList();
foreach (var order in unMarkOrders)
{
order.Info = null;
}
db.SaveChanges();
return new OrderList(distinctOrderList);
}
private bool StartOrders(OrderList orderList)
{
bool workDone = false;
using IWcsDbContext db = _dbContextFactory.GetDbContext();
for (int i = 0; i < orderList.Count; i++)
{
OrderListItem orderToBeScheduled = orderList[i];
try
{
// If there exists a cancelled sequencer orders for this LE this should always be started first
var cancelledSequencerOrder = db.OrdersHost
.ByLeNo(orderToBeScheduled.Le.LeNo)
.ByStatus(TransportOrderStatus.Initial)
.ByCancelledSequencerOrder()
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
.ToList();
if (cancelledSequencerOrder.FirstOrDefault() != null)
{
var cancelledOrderToBeScheduled = cancelledSequencerOrder.FirstOrDefault();
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {cancelledOrderToBeScheduled.OrdersHostId} will be scheduled since this is a cancelled sequencer order.");
_transportOrderService.ScheduleOrdersHost(cancelledOrderToBeScheduled.OrdersHostId);
//Order has been scheduled. Do not consider orders for the same LE.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
workDone = true;
continue;
}
// If there exists an order that has been flagged by HostBooking for departure
var departureFlaggedOrder = db.OrdersHost
.ByLeNo(orderToBeScheduled.Le.LeNo)
.ByStatus(TransportOrderStatus.Initial)
.IsDepartureReady()
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority,
o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
.ToList();
if (departureFlaggedOrder.FirstOrDefault() != null)
{
var departureOrderToBeScheduled = departureFlaggedOrder.FirstOrDefault();
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {departureOrderToBeScheduled.OrdersHostId} will be scheduled since it has been flagged for departure");
_transportOrderService.ScheduleOrdersHost(departureOrderToBeScheduled.OrdersHostId);
//Order has been scheduled. Do not consider orders for the same LE.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
workDone = true;
continue;
}
OrdersHost orderForSameLe = db.OrdersHost
.ByLeNo(orderToBeScheduled.Le.LeNo)
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress, TransportOrderStatus.InDestinationZone, TransportOrderStatus.InSequencer)
.OrderBy(o => o.Status != TransportOrderStatus.Pending ? 0 : 1) // get active order (if any), pending otherwise
.FirstOrDefault();
if (orderForSameLe != null && orderForSameLe.Id != orderToBeScheduled.OrdersHostId)
{
workDone = TryScheduleWithExistingOrder(orderForSameLe, db, orderToBeScheduled);
//Check equal LEs with open order only once
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
continue;
}
if (TryScheduleWithoutExistingOrder(orderToBeScheduled, db))
{
//Order has been scheduled. Do not consider orders for the same LE.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
workDone = true;
continue;
}
else
{
Log.Write(LogLevel.Debug, $"Destination: {orderToBeScheduled.Destination} has no ressources, skip all orders to this destination");
orderList.RemoveSubsequentWithEqualDestination(orderToBeScheduled);
}
}
catch(Exception ex)
{
Log.Write(LogLevel.Error, $"Can not set ordersHost {orderToBeScheduled.OrdersHostId} for LE {orderToBeScheduled.Le.LeNo} to pending. Due to Exception:");
Log.WriteException(ex);
}
//We can remove all subsequent orders with same LE, because the list is already ordered in a way that priorities are taken into account.
orderList.RemoveSubsequentWithEqualLeNo(orderToBeScheduled);
}
return workDone;
}
private bool TryScheduleWithoutExistingOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
{
if (CanScheduleOrder(orderToBeScheduled, db))
{
Log.Write(LogLevel.Debug, $"{nameof(OrdersHost)} with Id {orderToBeScheduled.OrdersHostId} will be scheduled, as there are no other {nameof(OrdersHost)} scheduled or active orders for the same LE.");
_transportOrderService.ScheduleOrdersHost(orderToBeScheduled.OrdersHostId);
return true;
}
return false;
}
private bool CanScheduleOrder(OrderListItem orderToBeScheduled, IWcsDbContext db)
{
// Orders with their associated LE not being in storage can always be scheduled...
//TODO Check for OnConveyor/InDestinationZone? IsInStorage() does not cover OnInputLeLifter/OnLhd/OnOutputLeLifter...
if (!orderToBeScheduled.Le.IsInStorage())
{
return true;
}
//...if LE is in storage, whether the order can be scheduled is determined by the amount of pending orders that may exist towards the destination and the destination's demand.
if (!SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.MaxPendingOrdersPerDestination, out int maxPendingOrdersPerDestination))
{
maxPendingOrdersPerDestination = 10;
}
Resource resourceDestination = db.ResourceSetting.GetResourceByName(orderToBeScheduled.Destination);
// for unmanaged resources, a demand of 1 is used.
int destinationDemand = resourceDestination?.Demand ?? 1;
int countPendingOrdersToDestination = db.OrdersHost.ByStatus(TransportOrderStatus.Pending).ByDestination(orderToBeScheduled.Destination).Count();
return destinationDemand - countPendingOrdersToDestination > 0 || countPendingOrdersToDestination - destinationDemand < maxPendingOrdersPerDestination;
}
private bool TryScheduleWithExistingOrder(OrdersHost orderForSameLe, IWcsDbContext db, OrderListItem orderToBeScheduled)
{
// We not want to overwrite existing orders. We wait until we are back in Storage. (This happened only about 40 times a day.
return false;
}
}
}

View File

@@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.Shared;
using Gebhardt.Shared.Process;
using Gebhardt.StoreWare.Wcs.Common;
using Gebhardt.StoreWare.Wcs.Common.Application.LeHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.StorageHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.Dao;
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 static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.ConveyorDispo
{
public class ToEmptyLeBuffer : ProcessWorker
{
private record AisleUtilization(AisleForLe Aisle, int Utilization);
private readonly IWcsDbContextFactory _dbContextFactory;
private readonly IDestinationService _destinationService;
private readonly ILeService _leService;
private readonly ITransportOrderService _transportOrderService;
public ToEmptyLeBuffer(int workInterval,
IDestinationService destinationService,
ILeService leService,
ITransportOrderService transportOrderService,
IAisleService aisleService,
IWcsDbContextFactory dbContextFactory)
: base(nameof(ToEmptyLeBuffer), workInterval, true)
{
_destinationService = destinationService;
_leService = leService;
_transportOrderService = transportOrderService;
_dbContextFactory = dbContextFactory;
}
public override bool DoWork()
{
try
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
//Retrieve aisles suited for storage (i.e. lifter/handover aisles are excluded)
List<AisleForLe> availableAisles = db.Aisle.GetAislesWithStorageCompartmentsReadyForRetrieval();
availableAisles = FilterForAllowedAisles(availableAisles);
SupplyBuffer(LeTypeName.MiniloadSmall, Constants.MfcAllDestinations.EMB_S, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL01);
SupplyBuffer(LeTypeName.MiniloadBig, Constants.MfcAllDestinations.EMB_M, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL01);
SupplyBuffer(LeTypeName.MiniloadSmall, Constants.MfcAllDestinationsOldSystem.ETXBU_S, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL02);
SupplyBuffer(LeTypeName.MiniloadBig, Constants.MfcAllDestinationsOldSystem.ETXBU_M, availableAisles.AsReadOnly(), Constants.MfcAllDestinations.AKL02);
return true;
}
catch (Exception exception)
{
Log.WriteException(exception);
return false;
}
}
/// <summary>
/// Refills each buffer with empty LEs depending on its demand.
/// First, it is checked if empty LEs on conveyer can be rerouted.
/// Second, empty LE demand is satisfied by retrieval from storage.
/// Last, empty LEs that have been explicitly requested by the WMS are retrieved.
/// </summary>
/// <param name="leType">the type of le to use</param>
/// <param name="buffers">List of buffers to refill.</param>
/// <param name="availableAisles">
/// List of available aisles (aisle, output place, storage device in aisle (if any) are
/// ready)
/// </param>
private void SupplyBuffer(LeTypeName leType, string buffer, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
if (string.IsNullOrEmpty(buffer))
{
Log.Write(LogLevel.Error, $"No buffers provided for {nameof(SupplyBuffer)}");
return;
}
using IWcsDbContext db = _dbContextFactory.GetDbContext();
var destinationBuffer = db.ResourceSetting.GetResourceByName(buffer);
double destinationBufferDemand = destinationBuffer.Demand;
double destinationBufferCapacity = destinationBuffer.Capacity + destinationBuffer.Overload;
double bufferFreePercentage = destinationBufferDemand / destinationBufferCapacity * 100.0;
//Setting to minimize crane usage
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.EmptyBoxesDemandThreshold, out int demandThreshold, 40);
if (destinationBufferDemand > 0)
{
RerouteLesOnConveyor(leType, buffer);
}
//If the buffers free space percentage is greater than the threshold settings value we also take boxes from storage
if (bufferFreePercentage > demandThreshold)
{
RefillBuffersFromStorage(leType, buffer, availableAisles, preferredStorageArea);
}
RefillBuffersWithRequestedLes(buffer, availableAisles, preferredStorageArea);
}
/// <summary>
/// Refills buffers with demand by rerouting LEs on conveyor that have a storage area as destination
/// (i.e. aisle selection has not been performed yet.
/// </summary>
private void RerouteLesOnConveyor(LeTypeName leType, string buffers)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<string> destinationsAllowingForRerouting = _destinationService.Where(d => d.IsStorageArea).Select(d => d.Name).ToList();
List<string> dontAllowforReroutingLastWhere = new List<string>() { MfcAllDestinationsOldSystem.IPT01, MfcAllDestinationsOldSystem.IPT02, MfcAllDestinationsOldSystem.IPT03, MfcAllDestinationsOldSystem.TOPUP, MfcAllDestinationsOldSystem.REP01, MfcAllDestinationsOldSystem.REP02, MfcAllDestinationsOldSystem.REP03, MfcAllDestinationsOldSystem.ETXBU_M, MfcAllDestinationsOldSystem.ETXBU_S };
//Transport orders with a storage area as destination
OrderList orders = new(db.OrdersHost
.ByDestination(destinationsAllowingForRerouting)
.ByType(TransportOrderType.Transport)
.ByStatus(TransportOrderStatus.Pending, TransportOrderStatus.InProgress)
.Where(o => o.Le.IsEmpty
&& (o.Le.Status == LeStatus.OnConveyor || o.Le.Status == LeStatus.InDestinationZone)
&& o.Le.Type == leType)
.AsEnumerable()
.Where(o => !o.Le.HasError() && !dontAllowforReroutingLastWhere.Contains(o.Le.LastWhere))
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag))
.ToList());
foreach (OrderListItem order in orders)
{
Resource destination = db.ResourceSetting.ByName(buffers).GetResourceWithHighestDemand();
if (destination is { Demand: > 0 })
{
if (db.OrdersMiniload.ByLeNo(order.Le.LeNo).Open().Any())
{
continue;
}
//Reroute LE
Log.Write(LogLevel.Debug, $"LE {order.Le.LeNo} will be rerouted to {destination.Name}.");
_leService.CancelOpenTransports(order.Le.LeNo, $"Rerouted to {destination.Name}");
Log.Write(LogLevel.Debug, $"LE {order.Le.LeNo} will be rerouted to {destination.Name}.");
// This is to mitigate filling a very long string into the source field. Having AKL01 for the old system is not a problem.
string source;
if (order.Le.LastWhere == null || order.Le.LastWhere.StartsWith("["))
{
source = Constants.MfcAllDestinations.AKL01;
}
else
{
source = order.Le.LastWhere;
}
int id = _leService.CreateTransport(order.Le.LeNo, source, destination.Name);
_transportOrderService.StartNextTransport(id, nameof(RerouteLesOnConveyor));
}
else if (order.Status == TransportOrderStatus.Pending)
{
_transportOrderService.StartNextTransport(order.OrdersHostId, info: nameof(RerouteLesOnConveyor));
}
}
}
/// <summary>
/// Refills one or more buffers with empty LEs from the storage.
/// First, Overload for each buffer is checked and updated via the SettingsManager
/// Then, for each buffer with demand, an aisle is selected and a retrieval order is created.
/// </summary>
private void RefillBuffersFromStorage(LeTypeName leType, string buffers, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<Resource> buffersWithDemand = db.ResourceSetting.ByName(buffers).GetResources().WithDemand().OrderByDescending(r => r.Demand).ToList();
foreach (Resource buffer in buffersWithDemand)
{
if (buffer.Demand > 0)
{
AisleForLe aisle = GetAisleForEmptyLeRetrieval(leType, availableAisles, preferredStorageArea);
if (aisle != null)
{
//Refill buffer
//OrdersHost has no storage area, i.e. aisle name must be unique by itself when used as source.
Log.Write(LogLevel.Debug, $"Empty LE of type {leType} will be retrieved from {aisle.AisleName} and sent towards {buffer.Name}.");
int id = _leService.CreateTransport(LeType.GetNextEmptyLeTypeForActual(leType).ToString(), aisle.AisleName, buffer.Name);
_transportOrderService.StartNextTransport(id, nameof(RefillBuffersFromStorage));
}
}
}
}
/// <summary>
/// Refills buffers according to a request received from the WMS.
/// An initial <see cref="OrdersHost" /> has already been created when handling the request.
/// Here, select aisle, start <see cref="OrdersHost" /> and create <see cref="OrdersMiniload" />.
/// </summary>
private void RefillBuffersWithRequestedLes(string buffers, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
//Initial transport orders for empty LEs (requested by the WMS)
OrderList orders = new(db.OrdersHost
.ByDestination(buffers)
.ByStatus(TransportOrderStatus.Initial)
.ByType(TransportOrderType.Transport)
.OnlyNextEmpty()
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority, o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag))
.ToList());
foreach (OrderListItem order in orders)
{
AisleForLe aisle = GetAisleForEmptyLeRetrieval(LeType.GetActualLeTypeForNextEmpty(order.Le.Type), availableAisles, preferredStorageArea);
if (aisle != null)
{
//OrdersHost has no storage area, i.e. aisle name must be unique by itself when used as source.
Log.Write(LogLevel.Debug, $"Requested empty LE of type {order.Le.Type} will be retrieved from {aisle.AisleName} and sent towards {order.Destination}.");
_transportOrderService.ScheduleOrdersHost(order.OrdersHostId, aisle.AisleName, nameof(RefillBuffersWithRequestedLes));
_transportOrderService.StartNextTransport(order.OrdersHostId, nameof(RefillBuffersWithRequestedLes));
}
}
}
private AisleForLe GetAisleForEmptyLeRetrieval(LeTypeName leTypeName, IReadOnlyList<AisleForLe> availableAisles, string preferredStorageArea)
{
using IWcsDbContext db = _dbContextFactory.GetDbContext();
List<AisleForLe> availableForLeRetrieval = availableAisles.ToList();
//Left outer join empty LEs with open orders miniload, then exclude LEs with open orders.
var emptyLesInStorageWithoutOpenOrders = db.Le
.ByStatus(LeStatus.InStorage)
.Where(l => !l.Location.IsLocked)
.ByType(leTypeName)
.Empty()
.GroupJoin(db.OrdersHost.Open(),
le => le.LeNo,
o => o.LeNo,
(le, ordersHost) => new { Le = le, OrdersHost = ordersHost })
.SelectMany(j => j.OrdersHost.DefaultIfEmpty(),
(l, o) => new { l.Le, OrdersHost = o })
.Where(j => j.OrdersHost == null)
.ToList();
if (emptyLesInStorageWithoutOpenOrders.Count > 0)
{
Log.Write(LogLevel.Info, $"empty Les found: {emptyLesInStorageWithoutOpenOrders.Count}");
}
Dictionary<AisleForLe, int> aislesWithEmptyLes = emptyLesInStorageWithoutOpenOrders.GroupBy(xx => xx.Le.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count());
Dictionary<AisleForLe, int> aislesWithActiveNextEmptyMiniload = db.OrdersMiniload.Open().ByLeNo(LeType.GetNextEmptyLeTypeForActual(leTypeName).ToString()).ToList()
.GroupBy(xx => xx.Aisle).ToDictionary(xx => xx.Key, xx => xx.Count());
//Leave only aisles containing LEs that can be retrieved
availableForLeRetrieval.RemoveAll(a => !aislesWithEmptyLes.ContainsKey(a) || (aislesWithEmptyLes.ContainsKey(a) && aislesWithActiveNextEmptyMiniload.ContainsKey(a) && aislesWithEmptyLes[a] - aislesWithActiveNextEmptyMiniload[a] <= 0));
// See if we can remove all other storage areas. If not, allow other.
if (availableForLeRetrieval.Any(xx => xx.StorageArea != preferredStorageArea)
&& availableAisles.Any(x => x.StorageArea == preferredStorageArea))
{
availableForLeRetrieval.RemoveAll(xx => xx.StorageArea != preferredStorageArea);
}
//Build one structre that has all information
Dictionary<AisleForLe, (int EmptyBoxes, int OrderedEmpties)> aislesWithNumbers = new Dictionary<AisleForLe, (int emptyBoxes, int orders)>();
foreach (var aisle in availableForLeRetrieval.Distinct())
{
int emptyBoxes = 0;
int orderedEmpties = 0;
if (aislesWithEmptyLes.ContainsKey(aisle))
{
emptyBoxes = aislesWithEmptyLes[aisle];
}
if (aislesWithActiveNextEmptyMiniload.ContainsKey(aisle))
{
orderedEmpties = aislesWithEmptyLes[aisle];
}
aislesWithNumbers.Add(aisle, (emptyBoxes, orderedEmpties));
}
AisleUtilization currentMinUtilization = null;
//Check MLS before Crane (as longterm MLS should be used for filled boxes), and we want more space in MLS
foreach (AisleForLe aisle in aislesWithNumbers.OrderBy(ar => ar.Key.AisleName.StartsWith("C")).ThenByDescending(ar => ar.Value.EmptyBoxes - ar.Value.OrderedEmpties).Select(ar => ar.Key))
{
AisleForLe inputAisle = db.AisleForLe.GetInputAisle(aisle);
int lesTowardsAisle = db.OrdersConveyor.Active().ByDestination(inputAisle.AisleName).Count();
//TODO include input/output le lifter orders?
//TODO include OrdersHost for orders that have no ordersMiniload yet?
int openOrdersForAisle = db.OrdersMiniload.Open().ByAisle(aisle.AisleName, aisle.StorageArea).Count();
int totalUtilization = openOrdersForAisle + lesTowardsAisle;
//We allow a utilization of 4 orders to be neglected, so we put more relevance on the number of totes
if (currentMinUtilization == null || currentMinUtilization.Utilization > totalUtilization + 4)
{
currentMinUtilization = new AisleUtilization(aisle, totalUtilization);
}
}
//Select aisle with lowest utilization
return currentMinUtilization?.Aisle;
}
/// <summary>
/// Filter depending on OnlyEmptiesToOldStorage setting:
/// If setting is true, get all empty LEs from old storage if available
/// </summary>
/// <param name="availableAisles"></param>
/// <param name="le"></param>
/// <returns></returns>
private List<AisleForLe> FilterForAllowedAisles(List<AisleForLe> availableAisles)
{
SettingsManager.GetParsedValue(ConstantsCommon.SettingNames.OnlyEmptiesToOldStorage, out bool onlyEmptiesToOldStorage, false, true);
if (!onlyEmptiesToOldStorage)
{
return availableAisles;
}
//get all empties from AKL02 if possible
if (availableAisles.Any(a => a.StorageArea == Constants.MfcAllDestinations.AKL02))
{
return availableAisles.Where(a => a.StorageArea == Constants.MfcAllDestinations.AKL02).ToList();
}
//Fallback get from available aisles
else
{
return availableAisles;
}
}
}
}

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle Aisle in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
Aisle(StorageArea [PK] string not_null, AisleName [PK] string not_null, Type string not_null, IsReady bool not_null, IsBlocked bool not_null, VerticalPosition int not_null, HorizontalPosition int not_null, Depth int not_null, MaintenanceLevel int not_null, Description string null, HandlingUnitType string not_null, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Timestamp datetime not_null, Process string not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle Destination in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
Destination(Name [PK] string not_null, LeNo [FK] string, Status string not_null, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Timestamp datetime not_null, Process string not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle FromWms in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
FromWms(Id [PK] int not_null, RefId [FK] int, IsShellEmpty bool, RequestId int, RequestType string, RecordType string, MovementType string, Status string, IdOrderWmsHead int, IdOrderWms int, IdOrderWmsPos int, LeNo string, Subdevision string, LeType string, ArticleNo string, Source string, Destination string, Count int, Rotation string, IsLeEmpty bool, Priority datetime, Weight int, HasError string, Location string, HasTransportError bool, ErrorInterface string, HasLeError bool, StorageArea string, WeightPositionsMin int, WeightPositionsMax int, Device string, Aisle string, Position string, Cancelled bool, AbcArea string, IsSignalActive bool, HorizontalPosition int, ArticleTag string, Width int, Height int, Length int, IsDirectPicking bool, Sequence int, Username string, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle Le in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
Le(LeNo [PK] string not_null, Type [FK] string not_null, AbcArea string, Status string not_null, Subdivision string not_null, IsEmpty bool not_null, AisleName [FK] string, StorageArea [FK] string, PrefferedStorageArea string, LocationId [FK] string, LocationDepth int, HasErrorTransportOrder bool not_null, HasErrorLabeling bool not_null, HasErrorNoRead bool not_null, HasErrorHeight bool not_null, HasErrorWidth bool not_null, HasErrorWeight bool not_null, Height int, Length int, Width int, Weight int, WeightMin int, WeightMax int, LastWhere string, LastWhen datetime, LastMovement string, Username string, ArticleTag string, IsHulnOldSystem bool, L8OrderCreated datetime, L8OrderFinished datetime, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Timestamp datetime not_null, Process string not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle LeType in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
LeType(Name [PK] string not_null, Depth int not_null, Width int not_null, Height int not_null, TypeSrc int not_null, TareWeight int not_null, TypeConveyor string, MaxWeight int not_null, TypePrefix string, TypeRange string, UseCartonTray bool not_null, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Timestamp datetime not_null, Process string not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle Location in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
Location(LocationId [PK] string not_null, StorageArea [PK] [FK] string not_null, AisleName [PK] [FK] string not_null SideSrc int not_null, HorizontalWay int not_null, VerticalWay int not_null, Position int not_null, Field string, DepthMax int not_null, DepthFree int not_null, Height int not_null, IsLocked bool not_null, AbcArea string, Type string not_null, Priority int not_null, Zone int not_null, LastLe string, Username string, IsLocationTestActive bool not_null, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Timestamp datetime not_null, Process string not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle OrdersConveyor in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
OrdersConveyor(Id [PK] int not_null, OrdersHostId [FK] int not_null, Source string not_null, Destination string not_null, PalletizingDestinations string, LeNo [FK] string not_null, Status string not_null, StartTime datetime, Error string, IsManual bool not_null, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle OrdersMiniload in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
OrdersMiniload(Id [PK] int not_null, OrdersHostId [FK] int not_null, AisleName [FK] string not_null, DeviceName [FK] string not_null, StorageArea [FK] string not_null, Type string not_null, LeNo [FK] string, Status string not_null, StatusSrc string, IdSubOrder int not_null, TotalOrders int not_null, LoadDevice string, Error string, IsSourceBooked bool not_null, IsDestinationBooked bool not_null, IsManual bool not_null, Priority int not_null, StartTime datetime, Source_LocationId string, Source_Depth int, Destination_LocationId string, Destination_Depth int, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle OrdersMiniload in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
ResourceSettings(Name [PK] string not_null, Type string, Capacity, int not_null, Overload int not_null, Creator string, Created datetime, CcuVersion int, Timestamp datetime, Process string)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -1,6 +0,0 @@
## Aufbau Tabelle StorageDevice in Relationenschreibweise
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
StorageDevice(StorageArea [PK] [FK] string not_null, DeviceName [PK] string not_null, AisleName [FK], Type string not_null, Status string not_null, OperationMode string not_null, ClearingIncrement int not_null, HasCriticalFault bool not_null, HasCamera bool not_null, IsLockedinAisle bool not_null, Description string, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Timestamp datetime not_null, Process string not_null)

View File

@@ -3,4 +3,4 @@
### Schema: TabellenName:(AttributName DatenTyp Restriktion)
### Schlüssel: PK: Primärschlüssel, FK: Fremdschlüssel
OrdersHost(Id [PK] int not_null, LeNo [FK] string not_null, Type string not_null, Source string not_null, Destination string not_null, HostDestination string not_null, Status string not_null, IdOrderWmsHead int, IdOrderWms int, IdOrderWmsPos int, Priority int not_null, PriorityDate datetime, StartTime datetime, Info string, Error string, IsEmptyLeRequest bool not_null, SequenceWms int, IsDirectPicking bool, IsStolen bool, SequencerRetrievalTime datetime, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null)
OrdersHost(Id [PK] int not_null, LeNo [FK] string not_null, Type string not_null, Source string not_null, Destination string not_null, HostDestination string not_null, Status string not_null, IdOrderWmsHead int, IdOrderWms int, IdOrderWmsPos int, Priority int not_null, PriorityDate datetime, StartTime datetime, Info string, Error string, IsEmptyLeRequest bool not_null, SequenceWms int, IsDirectPicking bool, IsStolen bool, SequencerRetrievalTime datetime, Creator string not_null, Created datetime not_null, CcuVersion int not_null, Process string not_null, Timestamp datetime not_null, DepartureFlag bool, DepartureLocation string, )

BIN
03_Realisierung/HostBooking/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,147 +1,160 @@
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;
}
}
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)
.Where (o => o.DepartureFlag != true)
.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);
// Flags order for departure so that ConveyorDispo can restart it.
currentOrderToPlace.DepartureFlag = true;
currentOrderToPlace.DepartureLocation = message.Position;
db.SaveChanges();
Log.Write(LogLevel.Info, $"OrdersHost for {message.LeNo} to {message.Position} flagged for departure by DepartureNotification.");
return true;
}
}
else
{
var test = db.OrdersHost.ByLeNo(message.LeNo).FirstOrDefault();
var otherPicOrder = db.OrdersHost.ByLeNo(message.LeNo)
.ByStatus(TransportOrderStatus.Initial)
.Where(o => o.DepartureFlag != true)
.ApplyWmsOrdering()
.FirstOrDefault();
if (otherPicOrder != null)
{
// _transportOrderService.StartNextTransport(otherPicOrder.Id);
// DepatureFlag set to true to signal processing through ConveyorDispo instead of HostBooking
otherPicOrder.DepartureFlag = true;
otherPicOrder.DepartureLocation = message.Position;
db.SaveChanges();
return true;
}
//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

@@ -1,35 +0,0 @@
## Structure
The standard process starts the following threads
| Threads | Description |
|---------|-------------|
| ToEmptyLeBuffer | supplies empty HU to workstations/buffers (typically for goods receipt) |
| StartInitialOrdersHost | schedules OrdersHost (bring them from status Initial to Pending) |
| LoopOverloadDistribution | Updates ResourceSetting.Overload based on the number of OrdersHost's destinations. |
| OrderManager | starts OrdersHosts in status Pending |
The worker-thread run independently from each other and the worker must be such that they not operate on the same Orders or Le.
## Worker *ToEmptyLeBuffer*
3 Steps:
1. try to reroute empty LE already on the conveyor on the way to storage to match the demand of the buffer
2. find LE in storage to match the demand of the buffer
3. Fulfill explicit orders from WMS for LE
- search for OrdersHost in status Initial and as LeNo a NextEmtyLe-name
- find an empty LE in storage and assign it to the OrderHost
- start the OrderHost
## Worker *StartInitialOrdersHost*
- If an order for the same LE of type Transport is active, replace the it with the new order
- If an order for the same LE of type TransportHost is active and the destination is the storage, replace the it with the new order
- schedule the OrderHost if no other order for the LE is active, and
- the Le is on the conveyor or
- the Le is in storage and the destination has demand
## Worker *OrderManager*
Starts the orders, depending on whether the destination is accessible (e.g. aisle not ready) or there is demand (e.g. for workstations), the priority of the orders and so on. Orders are considered in the following sequence:
- orders for LE on the conveyor
- orders to destinations with resources management
- orders to destinations without resources management

View File

@@ -1,26 +0,0 @@
## Overview
This process receives messages via the HostMessageFromWmsService and processes them.
## Message *AcknowledgeTransportCompleted*
The process finishes corresponding OrdersHost, OrdersConveyor, and OrdersMiniload entries (if they exist) and sends TordDelete telegrams to the affected devices.
## Message *CancelRequestForTransportOrder*
The process cancels corresponding OrdersHost, OrdersConveyor, and OrdersMiniload entries (if they exist) and sends TordDelete telegrams to the affected devices.
## Message *ChangePtlSignalState*
This is essentially forwared to the PLC: switch a PTL light on or off.
## Message *DepartureNotification*
Upon receipt, the process creates an OrdersHost entry or starts an existing one. Also, a corrseponding signal is sent to the PLC.
## Message *HuChange*
This message signals changes to a HU (e.g. type, the abc area, if it is empty, the subdivision type, and others)
## Message *RequestEmptyHuReport*
Depending on the request type, the WCS collects information on empty HU on the conveyor or in storage and replies with an EmptyHuReport.
## Message *SupplyRequestEmptyHu*
For this message, the process creates OrdersHost entries for the requestes HU type (not with explicit HU numbers but stand-in names). Process ConveyorDispo later selects the HU and starts the order.
## Message *UnsupportedHostMessage*
is a stand.in message for unknown message types.

View File

@@ -1,8 +0,0 @@
# WCS
### Process overview
| Process | Responsibilities |
|---------|------------------|
| ConveyorDispo | Scheduling OrdersHosts and create OrdersConveyor and/or OrdersMiniload|
| HostBooking | Process messages from an ERP system (create OrdersHost) |

View File

@@ -1,7 +0,0 @@
# Interfaces - external
* **To ERP**
* **DB communication** via Host.FromErp / Host.ToErp tables
* **WebAPI** via HostComWebServiceServer / HostComWebServiceClient
* **SAP IDoc;** sending and receiving SAP Idoc's
* **SAP RFC calls;** calling RFC's in the SAP; providing RFC server for call's from SAP

View File

@@ -1,10 +0,0 @@
# Interfaces - internal
* **To the conveyor and storage device PLC's**
* **TCP/IP telegrams WCS saves/reads telegrams in DB, Communication process does the actual sending/receiving.**
* Two ports for each device; one for sending telegrams; one for receiving telegrams
* Configuration of the telegrams via TelegramConfigurator; creates the source code
* **To WMS**
* **WebAPI;** configurable via HostConfigurator; creates the source code
* RestApiServer for receiving messages from WMS
* RestApiClient for sending messages to WMS

View File

@@ -1,20 +0,0 @@
# Outgoing Goods (Example for OLS roaming captive) - Gebhardt
| WMS | WCS | Device |
| :--- | :--- | :--- |
| Processing outgoing goods order results into TransportOrder to KAP02 | | |
| | `-->` | |
| | Tord/HU:10000005/Src:04-1-002-0-07-2/Dest:04-LD01-1<br>Tord/HU:10000005/Src:04-LD01-1/Dest:04-OP07-2 ... | |
| | | `-->` to S0404 (Ols) |
| | | `<--` **Pick**/HU:10000005/Src:04-1-002-0-07-2/Dest:04-LD01-1<br>**Drop** |
| | | `-->` to O0401 (Output Le Lifter) |
| | | `<--` **Pick**/HU:10000005/Src:04-LD01-1/Dest:04-OP07-2<br>**Drop** |
| | `TordDelete` (HU:10000005/Dest:04-OP07-2) | |
| | `Tord` (HU:10000005/Dest:KAP02) | |
| | | `-->` to BFT01 (Conveyor) |
| | | `<--` **PosPass**/HU:10000005/Pos:SC109 |
| | | `<--` **PosPass**/HU:10000005/Pos:SC110 |
| | | `<--` **PosPass**/HU:10000005/Pos:SC103 |
| | | `<--` **ZoneEntry**/HU:10000005/Zone:KAP02 |
| | | `<--` **Arrival**/HU:10000005/Pos:KAP02 |
| ArrivalNotification KAP02 | `<--` | |

View File

@@ -1,27 +0,0 @@
# Telegrams to Storage Devices, Conveyor
**Telegrams are ASCII and meant to be readable**
## Telegram Frame:
### To Crane:
`/Seq:001/Send:StoreWare/Rec:CRA-01-02-AKL1/Time:2025-05-06T10:30:00/<Telegram Body>/End/`
### Ack:
`/Seq:001/Send:CRA-01-02-AKL1/Rec:StoreWare/Time:2025-05-06T10:30:01/>/End/`
## Telegram Body:
### Simple:
`/Function:Pick/HU:10000005/Src:04-1-002-0-07-2/Dest:04-LD01-1/ToID:20000005/`
### More Complex:
`/Function:Store/HU:10000005/Src:04-LD01-1/Dest:04-1-002-0-07-2/ToID:20000005/Type:Box/Len:600/Wid:400/Hgt:300/Wgt:5000/WgtUnit:g/LHD:1/Eco:0/IsTO:1/OfTO:0/`
---
## Communication Logic (WCS <-> PLC)
* **GEBHARDT STOREWARE®** <---> **PLC / Unterlagerte Steuerung**
* Beide Seiten senden **Datentelegramm**
* Beide Seiten müssen mit **Empfangsquittung (Ack)** antworten.

Binary file not shown.

View File

@@ -0,0 +1,501 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.DbAccess.Base;
using Gebhardt.Shared;
using Gebhardt.Shared.DbAccess;
using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
public class OrdersHost : AutoIncrementEntity
{
private Le _le;
private ICollection<OrdersConveyor> _ordersConveyor;
private ICollection<OrdersMiniload> _ordersMiniload;
private ICollection<ResourceList> _resources;
internal OrdersHost(TransportOrderType type, string leNo, string source, string destination, int priority, int? idOrderWms, int? idOrderWmsHead, int? idOrderWmsPos, OrderTypeWms? typeWms)
{
Type = type;
LeNo = leNo;
Source = source;
Destination = destination;
Priority = priority;
IdOrderWms = idOrderWms;
IdOrderWmsHead = idOrderWmsHead;
IdOrderWmsPos = idOrderWmsPos;
TypeWms = typeWms;
OrdersConveyor = new List<OrdersConveyor>();
OrdersMiniload = new List<OrdersMiniload>();
Resources = new List<ResourceList>();
}
private OrdersHost()
{
}
private ILazyLoader LazyLoader { get; set; }
public string LeNo { get; private set; }
public Le Le
{
get => LazyLoader.Load(this, ref _le);
private set => _le = value;
}
public TransportOrderType Type { get; private set; }
public string Source { get; set; }
/// <summary>
/// the (possibly gross) destination for the Le: a storage area, a work place, etc.
/// </summary>
public string Destination { get; set; }
public string HostDestination { get; set; }
public TransportOrderStatus Status { get; private set; } = TransportOrderStatus.Initial;
public int? IdOrderWmsHead { get; private set; }
public int? IdOrderWms { get; private set; }
public int? IdOrderWmsPos { get; private set; }
// Currently not used at ETRA
public int Priority { get; set; }
public DateTime? PriorityDate { get; set; }
public DateTime? StartTime { get; private set; }
public string Info { get; set; }
public OrderTypeWms? TypeWms { get; }
public string Error { get; internal set; }
public bool IsEmptyLeRequest { get; set; }
// Used by sequencer to know what order to send out boxes in.
public int? SequenceWms { get; set; }
public bool? IsDirectPicking { get; set; }
//Used in deadlock situations when the same box is used in multiple Sequencers
public bool? IsStolen { get; set; }
public DateTime? SequencerRetrievalTime { get; set; }
// Used to signal ConveyorDispo that a depature request has been made by HostBooking (DepatureNotificationHandler)
public bool? DepartureFlag { get; set; }
// Used to store the location of a DepartureNotification handled by HostBooking (DepartureNotificationHandler)
public string? DepartureLocation { get; set; }
public ICollection<OrdersMiniload> OrdersMiniload
{
get => LazyLoader.Load(this, ref _ordersMiniload);
set => _ordersMiniload = value;
}
public ICollection<OrdersConveyor> OrdersConveyor
{
get => LazyLoader.Load(this, ref _ordersConveyor);
set => _ordersConveyor = value;
}
public ICollection<ResourceList> Resources
{
get => LazyLoader.Load(this, ref _resources);
set => _resources = value;
}
//Todo-Job: I don't like that the order is directly transmitted and no Tord is sent.
public OrdersConveyor StartConveyorOrderToNextDestination(IDestinationProperties newDestination = null, string reasonforredirect = null)
{
if (Status == TransportOrderStatus.InDestinationZone && newDestination is { BuffersEmptyLe: true })
{
throw new InvalidOperationException($"Cannot start {nameof(OrdersConveyor)} to next destination ({newDestination.Name}) as the {nameof(OrdersHost)} has already arrived.");
}
OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status));
if (openOrder != null)
{
openOrder.HandleFinished(newDestination);
}
OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination?.Name);
followingOrder.Transmit(reasonforredirect);
return followingOrder;
}
public void Redirect(IDestinationProperties newDestination = null, string reasonForRedirect = null)
{
if (newDestination != null && newDestination.Name != Destination)
{
OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status));
if (openOrder != null)
{
openOrder.Cancel($"Redirected to {newDestination.Name}");
UpdateResources(TransportOrderStatus.Cancelled, null);
}
Destination = newDestination.Name;
Info = reasonForRedirect;
OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination.Name);
followingOrder.Transmit();
}
}
public OrdersHost Reinitialize(string info)
{
Status = TransportOrderStatus.Initial;
Info = info;
StartTime = null;
return this;
}
public OrdersHost Postpone(IDestinationProperties destination, string info)
{
if (!TransportOrderStatusGroups.Waiting.Contains(Status))
{
throw new InvalidOperationException("Only orders that have not started yet can be postponed.");
}
if (!destination.BuffersEmptyLe)
{
Reinitialize(info);
}
else
{
Cancel(string.Empty, info);
}
return this;
}
public OrdersConveyor TransportToNio(string error)
{
Error = error;
OrdersConveyor nioTransport = OrdersConveyor?.FirstOrDefault();
if (nioTransport == null)
{
nioTransport = OrdersConveyorFactory.GetInstance(this).PendingForOrdersHost(error: error);
}
nioTransport.Transmit(error);
return nioTransport;
}
public OrdersHost Schedule(string source = null, string info = null)
{
if (TransportOrderStatusGroups.Waiting.Contains(Status))
{
Status = TransportOrderStatus.Pending;
Info = info;
if (source != null)
{
Source = source;
}
}
else
{
Log.Write(LogLevel.Error, $"cannot schedule due to Status={Status}; {this} ");
}
return this;
}
/// <summary>
/// set the OrdersHost to initial - if it's Status is InProgress or Transmitted
/// </summary>
/// <param name="info">informal reason</param>
/// <returns>true if rescheduled</returns>
public bool Reschedule(string info = null, string source = null)
{
if (Status != TransportOrderStatus.InProgress && Status != TransportOrderStatus.Transmitted)
{
Log.Write(LogLevel.Error, $"cannot reschedule due to Status={Status}; {this} ");
return false;
}
Status = TransportOrderStatus.Initial;
Info = info;
if (source != null) { Source = source; }
Log.Write(LogLevel.Info, $"Rescheduled {this}");
return true;
}
public OrdersHost Start()
{
if (TransportOrderStatusGroups.Waiting.Contains(Status))
{
Status = TransportOrderStatus.InProgress;
StartTime = DBDateTime.Now;
DepartureFlag = false;
DepartureLocation = null;
}
return this;
}
public void ManageResources()
{
if (!(Resources ??= new List<ResourceList>()).All(r => r.Status == TransportOrderStatus.Cancelled || r.Status == TransportOrderStatus.Finished) &&
Resources.Count != 0)
{
return;
}
// Is there an old resource that we need to reuse? (Primary key).
ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == Destination);
if (oldResource != null)
{
oldResource.Status = Status;
}
// If not create new.
else
{
Resources.Add(new ResourceList
{
OrdersHostId = Id,
LeNo = LeNo,
Name = Destination,
Status = Status
});
}
}
public void AddResourceEntry(TransportOrderStatus status, string destination)
{
// Is there an old resource that we need to reuse? (Primary key).
ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination);
if (oldResource != null)
{
oldResource.Status = status;
}
// If not create new.
else
{
Resources.Add(new ResourceList
{
OrdersHostId = Id,
LeNo = LeNo,
Name = destination,
Status = status
});
}
}
public void FinishResources(string destination)
{
// Is there an old resource that we need to reuse? (Primary key)
List<ResourceList> oldResource = Resources.Where(r => r.Name == destination).ToList();
foreach (ResourceList res in oldResource)
{
res.Status = TransportOrderStatus.Finished;
}
}
public void CancelResources(string destination)
{
List<ResourceList> oldResource = Resources.Where(r => r.Name == destination).ToList();
foreach (ResourceList res in oldResource)
{
Log.Write(LogLevel.Info, $"Cancelling previously assigned resource for Le {LeNo} to destination {destination}");
res.Status = TransportOrderStatus.Cancelled;
}
}
public OrdersHost EnterZone(IDestinationProperties destination)
{
if (!(TransportOrderStatusGroups.Active.Contains(Status) || Status == TransportOrderStatus.InSequencer))
{
return this;
}
Status = TransportOrderStatus.InDestinationZone;
UpdateResources(TransportOrderStatus.InDestinationZone, null);
return this;
}
/// <summary>
/// Set the status to <code>InDestinationZone</code>, update the resources and register the Le at the destination (if
/// known)
/// </summary>
/// <param name="destinationService"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public OrdersHost Arrive(IDestinationService destinationService)
{
if (TransportOrderStatusGroups.Waiting.Contains(Status))
{
throw new InvalidOperationException("Order has to start before it can arrive.");
}
if (TransportOrderStatusGroups.Complete.Contains(Status))
{
throw new InvalidOperationException("Order can not arrive if it is already finished.");
}
if (TransportOrderStatusGroups.Active.Contains(Status))
{
Status = TransportOrderStatus.InDestinationZone;
destinationService?.SetArrived(LeNo, Destination);
UpdateResources(TransportOrderStatus.InDestinationZone, null);
}
return this;
}
/// <summary>
/// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources
/// </summary>
public void Finish()
{
OrdersConveyor.ToList().ForEach(oc =>
{
if (TransportOrderStatusGroups.Open.Contains(oc.Status))
{
oc.HandleFinished();
using IWcsDbContext db = new WcsDbContextFactory().GetDbContext();
ConveyorTelegrams.SendTordDeleteEtra(db, oc.LeNo);
db.SaveChanges();
}
});
OrdersMiniload.ToList().ForEach(om =>
{
if (TransportOrderStatusGroups.Open.Contains(om.Status))
{
om.HandleFinished();
}
});
Status = TransportOrderStatus.Finished;
UpdateResources(TransportOrderStatus.Finished, null);
}
/// <summary>
/// Cancel this order, related OrdersMiniload, Orders Conveyor and clear the resources
/// </summary>
public void Cancel(string error, string info, bool cancelOrdersMiniload = true)
{
OrdersConveyor.ToList().ForEach(oc =>
{
if (TransportOrderStatusGroups.Open.Contains(oc.Status))
{
oc.Cancel(error);
}
});
if (cancelOrdersMiniload)
{
OrdersMiniload.ToList().ForEach(om =>
{
if (TransportOrderStatusGroups.Open.Contains(om.Status))
{
om.Cancel(error);
}
});
}
Status = TransportOrderStatus.Cancelled;
Error = error;
Info = info;
UpdateResources(TransportOrderStatus.Cancelled, null);
}
public OrdersMiniload GetPendingOrdersMiniload()
{
return OrdersMiniload?.SingleOrDefault(o => o.Status == TransportOrderStatus.Pending);
}
public OrdersMiniload GetOpenOrdersMiniload()
{
return OrdersMiniload?.FirstOrDefault(o => TransportOrderStatusGroups.Open.Contains(o.Status));
}
public OrdersMiniload GetActiveOrdersMiniload()
{
return OrdersMiniload?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status));
}
public OrdersConveyor GetActiveOrdersConveyor()
{
return OrdersConveyor?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status));
}
public void UpdateResources(TransportOrderStatus resourceListStatus, List<ResourceList> occupiedBySameLeButDifferentOrder)
{
//If a OrdersHost is Finished or Cancelled all ResourceList for this OH are affected, for other status only entries with same Destination
Resources
.Where(r => !TransportOrderStatusGroups.Complete.Contains(r.Status) && (r.Name == Destination || resourceListStatus == TransportOrderStatus.Finished || resourceListStatus == TransportOrderStatus.Cancelled))
.ToList()
.ForEach(r => r.Status = resourceListStatus);
occupiedBySameLeButDifferentOrder?.ForEach(r => r.Status = TransportOrderStatus.Cancelled);
}
public void UpdateSpecificResource(TransportOrderStatus resourceListStatus, string destination)
{
ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination);
if (oldResource == null)
{
return;
}
else
{
oldResource.Status = resourceListStatus;
}
}
public OrdersHost ReplaceNextEmptyLe(Le le)
{
if (!LeType.EmptyLeTypeNames.Contains(Le.Type))
{
throw new ArgumentException($"Reassigning an LE is only allowed to replace any of {nameof(LeType.EmptyLeTypeNames)}. Current LE: {le.LeNo}");
}
Le = le;
ResourceList resource = Resources.SingleOrDefault(r => r.Name == Destination);
if (resource != null)
{
resource.Le = le;
}
return this;
}
public override string ToString()
{
return $"{nameof(OrdersHost)}[{nameof(Id)}: {Id}, {nameof(LeNo)}: {LeNo}, {nameof(Type)}: {Type}, {nameof(Source)}: {Source}, {nameof(Destination)}: {Destination}, {nameof(Status)}: {Status}, {nameof(IdOrderWmsHead)}: {IdOrderWmsHead}, {nameof(IdOrderWms)}: {IdOrderWms}, {nameof(IdOrderWmsPos)}: {IdOrderWmsPos}]";
}
/// <summary>
/// Set the status to InDestinationZone regardless of the previous status. This is ONLY to be used for creating an orders host dummy when we get a no read on the outputs. Otherwise we cannot send an UnloadHuToAgv telegram to unload the board to an AGV.
/// </summary>
/// <returns></returns>
public OrdersHost ForceSetStatusInDestinationZone()
{
Status = TransportOrderStatus.InDestinationZone;
return this;
}
public OrdersHost ForceSetStatusInSequencer()
{
Status = TransportOrderStatus.InSequencer;
return this;
}
public OrdersHost ForceSetStatusInProgress()
{
Status = TransportOrderStatus.InProgress;
return this;
}
public OrdersHost ForceSetStatusTransmitted()
{
Status = TransportOrderStatus.Transmitted;
return this;
}
public bool IsSequencerCancelOrder()
{
return Source.Contains(WcsNames.SEQ) && Destination == Common.Constants.MfcAllDestinations.StorageLoop2;
}
}

View File

@@ -0,0 +1,52 @@
using Gebhardt.DbAccess.Base.Configuration;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using static Gebhardt.StoreWare.WcsWms.Constants.ModelConstants;
namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Configuration;
public class OrdersHostEntityConfiguration : BaseEntityConfiguration<OrdersHost>
{
public override void Configure(EntityTypeBuilder<OrdersHost> builder)
{
base.Configure(builder);
builder.HasIndex(e => new { e.LeNo, e.Status }, "I1_OrdersHost");
builder.HasIndex(e => new { e.Destination, e.Status }, "I2_OrdersHost");
builder.HasIndex(e => new { e.Type, e.Status }, "I3_OrdersHost");
builder.Property(e => e.Destination).IsRequired().HasMaxLength(OrderSourceDestLength);
builder.Property(e => e.Error).HasMaxLength(ErrorTextLength);
builder.Property(e => e.IdOrderWms);
builder.Property(e => e.IdOrderWmsHead);
builder.Property(e => e.IdOrderWmsPos);
builder.Property(e => e.DepartureFlag);
builder.Property(e => e.DepartureLocation);
builder.Property(e => e.Info).HasMaxLength(InfoTextLength);
builder.Property(e => e.Source).IsRequired().HasMaxLength(OrderSourceDestLength);
builder.Property(e => e.Status)
.IsRequired();
builder.Property(e => e.Type)
.IsRequired();
builder.Property(e => e.LeNo).IsRequired().HasMaxLength(LeNoLength);
builder.HasOne(e => e.Le).WithMany().HasForeignKey(e => e.LeNo).OnDelete(DeleteBehavior.Cascade);
builder.HasMany(e => e.Resources).WithOne(e => e.OrdersHost).HasForeignKey(e => e.OrdersHostId).IsRequired();
}
}

View File

@@ -0,0 +1,241 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Gebhardt.StoreWare.Wcs.Common.Dao;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model;
using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using static Gebhardt.StoreWare.Wcs.Common.Constants;
namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Queries
{
public static class OrdersHostQueries
{
public static IQueryable<OrdersHost> ByStatus(this IQueryable<OrdersHost> entity, params TransportOrderStatus[] states)
{
return entity.Where(o => states.Contains(o.Status));
}
public static IQueryable<OrdersHost> ByType(this IQueryable<OrdersHost> entity, params TransportOrderType[] types)
{
return entity.Where(o => types.Contains(o.Type));
}
public static IQueryable<OrdersHost> ByLeNo(this IQueryable<OrdersHost> entity, string leNo)
{
return entity.Where(o => o.LeNo == leNo);
}
/// <summary>
/// select entities with one of the given destinations
/// </summary>
/// <returns></returns>
public static IQueryable<OrdersHost> ByDestination(this IQueryable<OrdersHost> entity, IEnumerable<string> destinations)
{
return entity.Where(o => destinations.Contains(o.Destination));
}
/// <summary>
/// select entities which hs not one of the given destinations
/// </summary>
/// <returns></returns>
public static IQueryable<OrdersHost> ExcludeDestination(this IQueryable<OrdersHost> entity, IEnumerable<string> destinations)
{
return entity.Where(o => !destinations.Contains(o.Destination));
}
/// <summary>
/// select entities with the given destination
/// </summary>
/// <returns></returns>
public static IQueryable<OrdersHost> ByDestination(this IQueryable<OrdersHost> entity, string destination)
{
return entity.Where(o => destination == o.Destination);
}
/// <summary>
/// searches for OH
/// </summary>
/// <param name="entity"></param>
/// <param name="leNo"></param>
/// <returns></returns>
public static OrdersHost ActiveByLeNo(this IQueryable<OrdersHost> entity, string leNo)
{
return entity
.ByLeNo(leNo)
.SingleOrDefault(o => o.Status == TransportOrderStatus.InProgress || o.Status == TransportOrderStatus.InDestinationZone || o.Status == TransportOrderStatus.InSequencer);
}
public static IQueryable<OrdersHost> AllActiveByLeNo(this IQueryable<OrdersHost> entity, string leNo)
{
return entity
.ByLeNo(leNo)
.Where(o => o.Status == TransportOrderStatus.InProgress || o.Status == TransportOrderStatus.InDestinationZone || o.Status == TransportOrderStatus.InSequencer);
}
/// <summary>
/// find - if exists open orders host entries (Status: Initial, InProgress, InDestinationZone, Pending)
/// </summary>
/// <param name="entity"></param>
/// <param name="leNo">the le number</param>
/// <returns>null or an open orders host</returns>
public static IQueryable<OrdersHost> OpenByLeNo(this IQueryable<OrdersHost> entity, string leNo)
{
return entity
.Where(o => o.LeNo == leNo)
.Open()
//Better include this here in case one of the initial orders is started by that (done in many places)
.ApplyWmsOrdering();
}
/// <summary>
/// find - open orders host entries (Status: Initial, InProgress, InDestinationZone, Pending)
/// </summary>
/// <param name="entity"></param>
/// <returns>null or an open orders host</returns>
public static IQueryable<OrdersHost> Open(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => TransportOrderStatusGroups.Open.Contains(o.Status));
}
public static IQueryable<OrdersHost> Active(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => TransportOrderStatusGroups.Active.Contains(o.Status));
}
public static IQueryable<OrdersHost> ActivePendingOrInSequecer(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => TransportOrderStatusGroups.Active.Contains(o.Status) || o.Status == TransportOrderStatus.InSequencer || o.Status == TransportOrderStatus.Pending);
}
/// <summary>
/// Get all destinations for which there are orders host existing in any state
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static List<string> GetAllDestinations(this IQueryable<OrdersHost> entity)
{
return entity.Select(oh => oh.Destination).Distinct().ToList();
}
/// <summary>
/// Get all destinations for which there are orders host existing in the given states
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static List<string> GetAllDestinations(this IQueryable<OrdersHost> entity, params TransportOrderStatus[] states)
{
return entity.Where(oh => states.Contains(oh.Status)).Select(oh => oh.Destination).Distinct().ToList();
}
/// <summary>
/// Get all destinations for the orders host (maybe precede by an "Open()"!)
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static List<string> GetAllDestinations(this IQueryable<OrdersHost> entity, IEnumerable<string> destinations)
{
return entity.Where(oh => destinations.Contains(oh.Destination)).Select(oh => oh.Destination).Distinct().ToList();
}
public static IQueryable<OrderCountByDay> GetCountByDate(this IQueryable<OrdersHost> entity, TransportOrderType orderType, IEnumerable<DateTime> dates)
{
return entity.Where(oh => oh.Status == TransportOrderStatus.Finished && oh.Created.Date > dates.First().Date && oh.Type == orderType).GroupBy(oh => oh.Created.Date).OrderBy(g => g.Key).Select(r => new OrderCountByDay(r.Count(), r.Key));
}
public static IQueryable<OrderCountByDestination> GetCountByDestination(this IQueryable<OrdersHost> entity)
{
// This SQL is translated into a Lambda expression
DateTime today = DateTime.Now.Date;
DateTime yesterday = today.AddDays(-1);
return entity.Where(oh => oh.Status == TransportOrderStatus.Finished && oh.Created.Date >= yesterday && oh.Type == TransportOrderType.TransportHost).GroupBy(oh => oh.Destination).OrderBy(g => g.Key).Select(r => new OrderCountByDestination(
r.Key,
r.Sum(d => d.Created.Date == today ? 1 : 0),
r.Sum(d => d.Created.Date == yesterday ? 1 : 0)));
}
public static IQueryable<OrdersHost> ExcludeNextEmpty(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => o.LeNo != LeTypeName.NextEmptyMiniloadSmall.ToString() && o.LeNo != LeTypeName.NextEmptyMiniloadBig.ToString());
}
public static IQueryable<OrdersHost> OnlyNextEmpty(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => o.LeNo == LeTypeName.NextEmptyMiniloadSmall.ToString() || o.LeNo == LeTypeName.NextEmptyMiniloadBig.ToString());
}
/// <summary>
/// Special WMS ordering that takes the common Sequencer Retrieval Time into account
/// </summary>
/// <param name="entity"></param>
/// <param name="db"></param>
/// <returns></returns>
public static IOrderedQueryable<OrdersHost> ApplyWmsOrderingSequencerRetrievalTime(this IQueryable<OrdersHost> entity,
IWcsDbContext db)
{
var query = entity
.GroupJoin(
db.OrdersHost
.GroupBy(xx => xx.IdOrderWmsHead)
.Select(g => new
{
IdOrderWmsHead = g.Key,
SequencerRetrievalTime = g.Min(y => y.SequencerRetrievalTime)
}),
o => o.IdOrderWmsHead,
m => m.IdOrderWmsHead,
(o, m) => new { o, m }
)
.SelectMany(x => x.m.DefaultIfEmpty(), (x, m) => new { x.o, m })
.OrderByDescending(x => x.o.IsStolen)
.ThenByDescending(x => x.o.IsDirectPicking)
.ThenByDescending(x => x.m.SequencerRetrievalTime != null)
.ThenBy(x => x.m.SequencerRetrievalTime)
.ThenBy(x => x.o.Created)
.Select(x => x.o);
return (IOrderedQueryable<OrdersHost>) query;
}
public static IOrderedQueryable<OrdersHost> ApplyWmsOrdering(this IQueryable<OrdersHost> entity)
{
// Only order by Created, as WMS anyways only releases a limited number of orders. This saves us from running into dealocks.
//Sequencer RetrievalTime is set when box is retrieved from sequencer.
return entity
.OrderByDescending(o => o.IsStolen)
.ThenByDescending(o => o.IsDirectPicking)
.ThenByDescending(o => o.SequencerRetrievalTime != null)
.ThenBy(o => o.SequencerRetrievalTime)
.ThenBy(o => o.Created);
}
/// <summary>
/// Exclude all OHs where the LE is located in the sequencer, unless this order is a cancel order
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static IQueryable<OrdersHost> ExcludeOrdersInSequencer(this IQueryable<OrdersHost> entity)
{
//The comented orders are started in Seq_Dispo
return entity.Where(o => !(o.Le.Status == LeStatus.InStorage && o.Le.Aisle.Type == AisleType.Sequencer) /*|| (o.Source.Contains(WcsNames.SEQ) && o.Destination == MfcAllDestinations.StorageLoop2)*/);
}
/// <summary>
/// Filter by sequenceorders that has been cancelled
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static IQueryable<OrdersHost> ByCancelledSequencerOrder(this IQueryable<OrdersHost> entity)
{
return entity.Where(o => o.Source.Contains(WcsNames.SEQ) && o.Destination == MfcAllDestinations.StorageLoop2);
}
/// <summary>
/// Filters by all orders that have been marked for departure by HostBooking (DepartureNotificationHandler)
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public static IQueryable<OrdersHost> IsDepartureReady(this IQueryable<OrdersHost> entity) {
return entity.Where(o => o.DepartureLocation != null && o.DepartureFlag == true);
}
}
}

View File

@@ -1,32 +0,0 @@
# Overview
## Funktionale Aufgaben
### WCS (Warehouse Control System)
* Storage of HUs (Lagerung von Handling Units)
* Transport to /from workstations (Transport zu/von Arbeitsstationen)
* Management of empty Hus (Verwaltung von Leerbehältern)
* Weight/height checks (Gewichts-/Höhenkontrollen)
* Control of Conveyor, storage devices (Steuerung von Fördertechnik und Lagergeräten)
### WMS (Warehouse Management System)
* Content & structure of Hus (Not empty HU!) (Inhalt & Struktur von HUs - keine Leerbehälter)
* Goods receipt & exit processes (Wareneingangs- & Ausgangsprozesse)
* Disposal of material (Materialentsorgung)
* Stock taking, visual control (Inventur, Sichtkontrolle)
* Blocking of material (Sperren von Material)
---
## Systemarchitektur / Datenfluss
Die Systeme sind in folgender Hierarchie miteinander verbunden (bidirektionaler Datenaustausch):
1. **ERP system** (Enterprise Resource Planning)
* *verbunden mit:*
2. **WMS** (Warehouse Management System)
* *verbunden mit:*
3. **WCS** (Warehouse Control System)
* *verbunden mit der Hardware-Ebene:*
* **Conveyor PLCs** (SPS der Fördertechnik)
* **Storage devices** (Lagergeräte/Regalbediengeräte)

View File

@@ -1,100 +0,0 @@
# Taskboard | Abschlussarbeit Kai
## 02 HostBooking Analysieren
### Status: ⬛ Done
Feststellen wo (welche Bedingungen) im HostBooking Aufträge gestartet werden. V.a. Nachrichten TransportOrderCompleted und DepartureNotification sind relevant. Bitte ggfs. Behälter-Typen beachten.
INFO: Formlose Notizen mit: Code-Stelle, Bedingungen, Prozess/Szenario genügen
**Nächste Schritte:**
- [x] Quellcode des HostBooking-Prozesses lokalisieren
- [x] Analyse der Verarbeitung von 'TransportOrderCompleted' (Telegramm-Rückmeldung)
- [x] Analyse der 'DepartureNotification' (Abmeldung von Plätzen)
- [x] Identifikation von Stellen, an denen Aufträge direkt gestartet werden (Ziel: Zentralisierung)
- [x] Prüfung der Abhängigkeiten von Behälter-Typen (Container Types)
------------------------------------------
## 03 Konzept erstellen
### Status: 🟩 Active
Idee dokumentieren: Wie können die Code Stellen die einen Auftrag starten aus dem HostBooking so umgebaut werden, dass der ConveyorDispo den Start übernimmt. Am besten ins Ablauf-Diagramm aus (01 ConveyorDispo Analysieren) ergänzen.
------------------------------------------
## 04 Änderung Implementieren
### Status: ⬜ New
Ziel: HostBooking startet keine Aufträge selbst
HostBooking und ConveyorDispo anpassen.
------------------------------------------
## 05 Änderung Debuggen/Testen
### Status: ⬜ New
Änderugnen gegen die Emulation Testen Szenario:
- Normale Auslagerung, Kiste steht im Lager
- Kiste fährt schon zu einem Arbeitsplatz und bekommt weiteren Auftrag
- Kiste fährt gerade vom Arbeitsplatz zurück ins Lager und bekommt neuen Auftrag
- Kiste wird am Arbeitsplatz leer. (Kann über HuChange in der FromWms Schnittstelle "simuliert" werden, passiert im Testtool auch so manchmal)
------------------------------------------
## 06 Dokumentation
### Status: ⬜ New
Die Änderungen sollten mit einem "Warum" im Code per Kommentar dokumentiert sein.
Das Konzept sollte als Ablaufdiagramm dokumentiert sein.
Schriftliche Ausarbeitung nur im Maße wie es die IHK will.
------------------------------------------
## 00 Vorbereitung
### Status: ⬛ Done
- [x] Etra Repo Fork clonen
- [x] Zugang zur Etra Emulation prüfen
- [x] ConveyorDispo debuggen
------------------------------------------
## 00.5 ERD erstellen
### Status: ⬛ Done
Entity Relationship Diagramm der relevanten Tabellen der Datenbank erstellen.
- Destination
- LeType
- Le
- Location
- OrdersHost
- OrdersConveyor
- OrdersMiniload
- Aisle
- StorageDevice
------------------------------------------
## 01 ConveyorDispo Analysieren
### Status: ⬛ Done
Mit ConveyorDispo vertraut machen. Verständnis was macht "StartInitialOrders", was macht "OrderManager". Am besten kleines Ablaufdiagramm, dass Status Änderungen und notwendige Bedingungen dokumentiert.
Wichtig: Wo/Wann werden OrdersHost-Aufträge gestartet (Tord an SPS)?
------------------------------------------
"Status Legende: ⬜ New, 🟩 Active, ⬛ Done"