237 lines
9.8 KiB
C#
237 lines
9.8 KiB
C#
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;
|
|
}
|
|
|
|
} |