feat: initialize HostBooking and ConveyorDispo code structure and document project processes and database schema

This commit is contained in:
2026-05-06 11:51:58 +02:00
parent 5d856971cd
commit 95a5ea9b8b
35 changed files with 1878 additions and 2 deletions

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