refactor: migrate project structure by reorganizing realization code snippets into documentation and analysis categories.
This commit is contained in:
276
03_Realisierung/ConveyorDispo/OrderManager.cs
Normal file
276
03_Realisierung/ConveyorDispo/OrderManager.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
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> departureNotificationOrders = db.OrdersHost
|
||||
.ByStatus(TransportOrderStatus.InDestinationZone, TransportOrderStatus.Pending)
|
||||
.IsDepartureReady();
|
||||
|
||||
foreach (OrdersHost order in departureNotificationOrders)
|
||||
{
|
||||
// Restart order: Case when order has status InDestinationZone and has been flagged for departure by HostBooking (DepartureNotificationHandler)
|
||||
if (order.Status == TransportOrderStatus.InDestinationZone)
|
||||
{
|
||||
order.UpdateResources(TransportOrderStatus.InProgress, null);
|
||||
order.ForceSetStatusInProgress();
|
||||
order.Le.SetStatus(LeStatus.OnConveyor);
|
||||
_destinationService.SetLeRequestDeparture(order.Le.LeNo, order.DepartureLocation);
|
||||
// TODO Check if telegram can be sent from here
|
||||
ConveyorTelegrams.SendDepartureEtra(db, order.LeNo, order.DepartureLocation);
|
||||
order.DepartureFlag = false;
|
||||
order.DepartureLocation = null;
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
// Starting orders that are initial and were marked for departure by HostBooking (DepartureNotificationHandler)
|
||||
OrderList departureOrders = new(departureNotificationOrders
|
||||
.ByStatus(TransportOrderStatus.Pending)
|
||||
.OrderBy(o => o.StartTime)
|
||||
.Select(o => new OrderListItem(o.Id, o.Status, o.Le, o.Destination, o.Priority,
|
||||
o.IdOrderWmsHead, o.Created, o.HostDestination, o.DepartureFlag, o.DepartureLocation))
|
||||
.AsNoTracking()
|
||||
.ToList());
|
||||
workDone |= StartNextOrders(departureOrders);
|
||||
|
||||
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, o.DepartureFlag, o.DepartureLocation))
|
||||
.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, o.DepartureFlag, o.DepartureLocation))
|
||||
.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 , o.DepartureFlag, o.DepartureLocation))
|
||||
.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);
|
||||
// Should not change through Transportstart (flag is set to false after OrdersHost start) because boolean is saved inside OrderListItem
|
||||
if (order.DepartureFlag == true)
|
||||
{
|
||||
// Centralized from HostBooking (DepartureNotificationHandler) for otherPicOrder
|
||||
_destinationService.SetLeRequestDeparture(order.Le.LeNo, order.DepartureLocation);
|
||||
ConveyorTelegrams.SendDepartureEtra(db, order.Le.LeNo, order.DepartureLocation);
|
||||
db.Le.FirstOrDefault(l => l.LeNo == order.Le.LeNo)?.SetStatus(LeStatus.OnConveyor);
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user