Files
IHK-Projekt/02_Analyse_Konzept/OrdersHost Datenmodell/IST Zustand/OrdersHost.cs

493 lines
17 KiB
C#

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> _ordersConveyor;
private ICollection<OrdersMiniload> _ordersMiniload;
private ICollection<ResourceList> _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<OrdersConveyor>();
OrdersMiniload = new List<OrdersMiniload>();
Resources = new List<ResourceList>();
}
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; }
/// <summary>
/// the (possibly gross) destination for the Le: a storage area, a work place, etc.
/// </summary>
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; }
public ICollection<OrdersMiniload> OrdersMiniload
{
get => LazyLoader.Load(this, ref _ordersMiniload);
set => _ordersMiniload = value;
}
public ICollection<OrdersConveyor> OrdersConveyor
{
get => LazyLoader.Load(this, ref _ordersConveyor);
set => _ordersConveyor = value;
}
public ICollection<ResourceList> 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;
}
/// <summary>
/// set the OrdersHost to initial - if it's Status is InProgress or Transmitted
/// </summary>
/// <param name="info">informal reason</param>
/// <returns>true if rescheduled</returns>
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;
}
return this;
}
public void ManageResources()
{
if (!(Resources ??= new List<ResourceList>()).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<ResourceList> oldResource = Resources.Where(r => r.Name == destination).ToList();
foreach (ResourceList res in oldResource)
{
res.Status = TransportOrderStatus.Finished;
}
}
public void CancelResources(string destination)
{
List<ResourceList> 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;
}
/// <summary>
/// Set the status to <code>InDestinationZone</code>, update the resources and register the Le at the destination (if
/// known)
/// </summary>
/// <param name="destinationService"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
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;
}
/// <summary>
/// finish this order, related OrdersMiniload, Orders Conveyor and clear the resources
/// </summary>
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);
}
/// <summary>
/// Cancel this order, related OrdersMiniload, Orders Conveyor and clear the resources
/// </summary>
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<ResourceList> 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}]";
}
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
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;
}
}