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; } } /// /// Returns only the first order per LE /// /// /// Filtered List private OrderList OnlyFirstOrderPerLeNo(IWcsDbContext db, OrderList initialOrders) { List distinctOrderList = new (); List 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; } } }