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

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

BIN
02_Analyse_Konzept/.DS_Store vendored Normal file

Binary file not shown.

View File

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

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

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

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

View File

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

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

@@ -0,0 +1,66 @@
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);
}
}
}
}

View File

@@ -0,0 +1,237 @@
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;
}
}

View File

@@ -0,0 +1,205 @@
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;
}
}
}

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))
.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;
}
}
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

BIN
02_Analyse_Konzept/Datenbank/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

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

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

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

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

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

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

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

@@ -0,0 +1,6 @@
## Aufbau Tabelle OrdersHost in Relationenschreibweise
### 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)

View File

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

@@ -0,0 +1,6 @@
## 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)

View File

@@ -0,0 +1,6 @@
## 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

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

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

View File

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

BIN
02_Analyse_Konzept/HostBooking/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

View File

@@ -0,0 +1,493 @@
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; }
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;
}
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,48 @@
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.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();
}
}