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