using System; using System.Collections.Generic; using System.Linq; using Gebhardt.DbAccess.Base; using Gebhardt.Shared; using Gebhardt.Shared.DbAccess; using Gebhardt.StoreWare.Wcs.Common.Application.TransportHandling.Interfaces; using Gebhardt.StoreWare.Wcs.Common.DbAccess.Factories; using Gebhardt.StoreWare.Wcs.Common.DbAccess.Model.Enums; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Gebhardt.StoreWare.Wcs.Common.DbAccess.Model; public class OrdersHost : AutoIncrementEntity { private Le _le; private ICollection _ordersConveyor; private ICollection _ordersMiniload; private ICollection _resources; internal OrdersHost(TransportOrderType type, string leNo, string source, string destination, int priority, int? idOrderWms, int? idOrderWmsHead, int? idOrderWmsPos, OrderTypeWms? typeWms) { Type = type; LeNo = leNo; Source = source; Destination = destination; Priority = priority; IdOrderWms = idOrderWms; IdOrderWmsHead = idOrderWmsHead; IdOrderWmsPos = idOrderWmsPos; TypeWms = typeWms; OrdersConveyor = new List(); OrdersMiniload = new List(); Resources = new List(); } private OrdersHost() { } private ILazyLoader LazyLoader { get; set; } public string LeNo { get; private set; } public Le Le { get => LazyLoader.Load(this, ref _le); private set => _le = value; } public TransportOrderType Type { get; private set; } public string Source { get; set; } /// /// the (possibly gross) destination for the Le: a storage area, a work place, etc. /// public string Destination { get; set; } public string HostDestination { get; set; } public TransportOrderStatus Status { get; private set; } = TransportOrderStatus.Initial; public int? IdOrderWmsHead { get; private set; } public int? IdOrderWms { get; private set; } public int? IdOrderWmsPos { get; private set; } // Currently not used at ETRA public int Priority { get; set; } public DateTime? PriorityDate { get; set; } public DateTime? StartTime { get; private set; } public string Info { get; set; } public OrderTypeWms? TypeWms { get; } public string Error { get; internal set; } public bool IsEmptyLeRequest { get; set; } // Used by sequencer to know what order to send out boxes in. public int? SequenceWms { get; set; } public bool? IsDirectPicking { get; set; } //Used in deadlock situations when the same box is used in multiple Sequencers public bool? IsStolen { get; set; } public DateTime? SequencerRetrievalTime { get; set; } // Used to signal ConveyorDispo that a depature request has been made by HostBooking (DepatureNotificationHandler) public bool? DepartureFlag { get; set; } // Used to store the location of a DepartureNotification handled by HostBooking (DepartureNotificationHandler) public string? DepartureLocation { get; set; } public ICollection OrdersMiniload { get => LazyLoader.Load(this, ref _ordersMiniload); set => _ordersMiniload = value; } public ICollection OrdersConveyor { get => LazyLoader.Load(this, ref _ordersConveyor); set => _ordersConveyor = value; } public ICollection Resources { get => LazyLoader.Load(this, ref _resources); set => _resources = value; } //Todo-Job: I don't like that the order is directly transmitted and no Tord is sent. public OrdersConveyor StartConveyorOrderToNextDestination(IDestinationProperties newDestination = null, string reasonforredirect = null) { if (Status == TransportOrderStatus.InDestinationZone && newDestination is { BuffersEmptyLe: true }) { throw new InvalidOperationException($"Cannot start {nameof(OrdersConveyor)} to next destination ({newDestination.Name}) as the {nameof(OrdersHost)} has already arrived."); } OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status)); if (openOrder != null) { openOrder.HandleFinished(newDestination); } OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination?.Name); followingOrder.Transmit(reasonforredirect); return followingOrder; } public void Redirect(IDestinationProperties newDestination = null, string reasonForRedirect = null) { if (newDestination != null && newDestination.Name != Destination) { OrdersConveyor openOrder = OrdersConveyor.SingleOrDefault(oc => TransportOrderStatusGroups.Open.Contains(oc.Status)); if (openOrder != null) { openOrder.Cancel($"Redirected to {newDestination.Name}"); UpdateResources(TransportOrderStatus.Cancelled, null); } Destination = newDestination.Name; Info = reasonForRedirect; OrdersConveyor followingOrder = OrdersConveyorFactory.GetInstance(this).InitialForOrdersHost(openOrder?.Destination, newDestination.Name); followingOrder.Transmit(); } } public OrdersHost Reinitialize(string info) { Status = TransportOrderStatus.Initial; Info = info; StartTime = null; return this; } public OrdersHost Postpone(IDestinationProperties destination, string info) { if (!TransportOrderStatusGroups.Waiting.Contains(Status)) { throw new InvalidOperationException("Only orders that have not started yet can be postponed."); } if (!destination.BuffersEmptyLe) { Reinitialize(info); } else { Cancel(string.Empty, info); } return this; } public OrdersConveyor TransportToNio(string error) { Error = error; OrdersConveyor nioTransport = OrdersConveyor?.FirstOrDefault(); if (nioTransport == null) { nioTransport = OrdersConveyorFactory.GetInstance(this).PendingForOrdersHost(error: error); } nioTransport.Transmit(error); return nioTransport; } public OrdersHost Schedule(string source = null, string info = null) { if (TransportOrderStatusGroups.Waiting.Contains(Status)) { Status = TransportOrderStatus.Pending; Info = info; if (source != null) { Source = source; } } else { Log.Write(LogLevel.Error, $"cannot schedule due to Status={Status}; {this} "); } return this; } /// /// set the OrdersHost to initial - if it's Status is InProgress or Transmitted /// /// informal reason /// true if rescheduled public bool Reschedule(string info = null, string source = null) { if (Status != TransportOrderStatus.InProgress && Status != TransportOrderStatus.Transmitted) { Log.Write(LogLevel.Error, $"cannot reschedule due to Status={Status}; {this} "); return false; } Status = TransportOrderStatus.Initial; Info = info; if (source != null) { Source = source; } Log.Write(LogLevel.Info, $"Rescheduled {this}"); return true; } public OrdersHost Start() { if (TransportOrderStatusGroups.Waiting.Contains(Status)) { Status = TransportOrderStatus.InProgress; StartTime = DBDateTime.Now; DepartureFlag = false; DepartureLocation = null; } return this; } public void ManageResources() { if (!(Resources ??= new List()).All(r => r.Status == TransportOrderStatus.Cancelled || r.Status == TransportOrderStatus.Finished) && Resources.Count != 0) { return; } // Is there an old resource that we need to reuse? (Primary key). ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == Destination); if (oldResource != null) { oldResource.Status = Status; } // If not create new. else { Resources.Add(new ResourceList { OrdersHostId = Id, LeNo = LeNo, Name = Destination, Status = Status }); } } public void AddResourceEntry(TransportOrderStatus status, string destination) { // Is there an old resource that we need to reuse? (Primary key). ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination); if (oldResource != null) { oldResource.Status = status; } // If not create new. else { Resources.Add(new ResourceList { OrdersHostId = Id, LeNo = LeNo, Name = destination, Status = status }); } } public void FinishResources(string destination) { // Is there an old resource that we need to reuse? (Primary key) List oldResource = Resources.Where(r => r.Name == destination).ToList(); foreach (ResourceList res in oldResource) { res.Status = TransportOrderStatus.Finished; } } public void CancelResources(string destination) { List oldResource = Resources.Where(r => r.Name == destination).ToList(); foreach (ResourceList res in oldResource) { Log.Write(LogLevel.Info, $"Cancelling previously assigned resource for Le {LeNo} to destination {destination}"); res.Status = TransportOrderStatus.Cancelled; } } public OrdersHost EnterZone(IDestinationProperties destination) { if (!(TransportOrderStatusGroups.Active.Contains(Status) || Status == TransportOrderStatus.InSequencer)) { return this; } Status = TransportOrderStatus.InDestinationZone; UpdateResources(TransportOrderStatus.InDestinationZone, null); return this; } /// /// Set the status to InDestinationZone, update the resources and register the Le at the destination (if /// known) /// /// /// /// public OrdersHost Arrive(IDestinationService destinationService) { if (TransportOrderStatusGroups.Waiting.Contains(Status)) { throw new InvalidOperationException("Order has to start before it can arrive."); } if (TransportOrderStatusGroups.Complete.Contains(Status)) { throw new InvalidOperationException("Order can not arrive if it is already finished."); } if (TransportOrderStatusGroups.Active.Contains(Status)) { Status = TransportOrderStatus.InDestinationZone; destinationService?.SetArrived(LeNo, Destination); UpdateResources(TransportOrderStatus.InDestinationZone, null); } return this; } /// /// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources /// public void Finish() { OrdersConveyor.ToList().ForEach(oc => { if (TransportOrderStatusGroups.Open.Contains(oc.Status)) { oc.HandleFinished(); using IWcsDbContext db = new WcsDbContextFactory().GetDbContext(); ConveyorTelegrams.SendTordDeleteEtra(db, oc.LeNo); db.SaveChanges(); } }); OrdersMiniload.ToList().ForEach(om => { if (TransportOrderStatusGroups.Open.Contains(om.Status)) { om.HandleFinished(); } }); Status = TransportOrderStatus.Finished; UpdateResources(TransportOrderStatus.Finished, null); } /// /// Cancel this order, related OrdersMiniload, Orders Conveyor and clear the resources /// public void Cancel(string error, string info, bool cancelOrdersMiniload = true) { OrdersConveyor.ToList().ForEach(oc => { if (TransportOrderStatusGroups.Open.Contains(oc.Status)) { oc.Cancel(error); } }); if (cancelOrdersMiniload) { OrdersMiniload.ToList().ForEach(om => { if (TransportOrderStatusGroups.Open.Contains(om.Status)) { om.Cancel(error); } }); } Status = TransportOrderStatus.Cancelled; Error = error; Info = info; UpdateResources(TransportOrderStatus.Cancelled, null); } public OrdersMiniload GetPendingOrdersMiniload() { return OrdersMiniload?.SingleOrDefault(o => o.Status == TransportOrderStatus.Pending); } public OrdersMiniload GetOpenOrdersMiniload() { return OrdersMiniload?.FirstOrDefault(o => TransportOrderStatusGroups.Open.Contains(o.Status)); } public OrdersMiniload GetActiveOrdersMiniload() { return OrdersMiniload?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status)); } public OrdersConveyor GetActiveOrdersConveyor() { return OrdersConveyor?.SingleOrDefault(o => TransportOrderStatusGroups.Active.Contains(o.Status)); } public void UpdateResources(TransportOrderStatus resourceListStatus, List occupiedBySameLeButDifferentOrder) { //If a OrdersHost is Finished or Cancelled all ResourceList for this OH are affected, for other status only entries with same Destination Resources .Where(r => !TransportOrderStatusGroups.Complete.Contains(r.Status) && (r.Name == Destination || resourceListStatus == TransportOrderStatus.Finished || resourceListStatus == TransportOrderStatus.Cancelled)) .ToList() .ForEach(r => r.Status = resourceListStatus); occupiedBySameLeButDifferentOrder?.ForEach(r => r.Status = TransportOrderStatus.Cancelled); } public void UpdateSpecificResource(TransportOrderStatus resourceListStatus, string destination) { ResourceList oldResource = Resources.FirstOrDefault(r => r.OrdersHostId == Id && r.Name == destination); if (oldResource == null) { return; } else { oldResource.Status = resourceListStatus; } } public OrdersHost ReplaceNextEmptyLe(Le le) { if (!LeType.EmptyLeTypeNames.Contains(Le.Type)) { throw new ArgumentException($"Reassigning an LE is only allowed to replace any of {nameof(LeType.EmptyLeTypeNames)}. Current LE: {le.LeNo}"); } Le = le; ResourceList resource = Resources.SingleOrDefault(r => r.Name == Destination); if (resource != null) { resource.Le = le; } return this; } public override string ToString() { return $"{nameof(OrdersHost)}[{nameof(Id)}: {Id}, {nameof(LeNo)}: {LeNo}, {nameof(Type)}: {Type}, {nameof(Source)}: {Source}, {nameof(Destination)}: {Destination}, {nameof(Status)}: {Status}, {nameof(IdOrderWmsHead)}: {IdOrderWmsHead}, {nameof(IdOrderWms)}: {IdOrderWms}, {nameof(IdOrderWmsPos)}: {IdOrderWmsPos}]"; } /// /// Set the status to InDestinationZone regardless of the previous status. This is ONLY to be used for creating an orders host dummy when we get a no read on the outputs. Otherwise we cannot send an UnloadHuToAgv telegram to unload the board to an AGV. /// /// public OrdersHost ForceSetStatusInDestinationZone() { Status = TransportOrderStatus.InDestinationZone; return this; } public OrdersHost ForceSetStatusInSequencer() { Status = TransportOrderStatus.InSequencer; return this; } public OrdersHost ForceSetStatusInProgress() { Status = TransportOrderStatus.InProgress; return this; } public OrdersHost ForceSetStatusTransmitted() { Status = TransportOrderStatus.Transmitted; return this; } public bool IsSequencerCancelOrder() { return Source.Contains(WcsNames.SEQ) && Destination == Common.Constants.MfcAllDestinations.StorageLoop2; } }