123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- using System;
- using Comal.Classes;
- using Comal.Stores;
- using InABox.Core;
- using InABox.Database;
- using System.Collections.Generic;
- using System.Linq;
- namespace PRSStores;
- public enum JobRequisitionItemAction
- {
- Created,
- Updated,
- Deleted,
- None
- }
- public class JobRequisitionItemStore : BaseStore<JobRequisitionItem>
- {
- protected override void BeforeSave(JobRequisitionItem item)
- {
- if (item.ID != Guid.Empty)
- {
- if (CalculateStatus(this, item))
- item.Notes += $"{(!String.IsNullOrWhiteSpace(item.Notes) ? "\n" : "")}Status updated to {item.Status.ToString().SplitCamelCase()} because the record was changed";
- }
- base.BeforeSave(item);
- }
- protected override void AfterSave(JobRequisitionItem entity)
- {
- base.AfterSave(entity);
- if(entity.HasOriginalValue(x => x.Cancelled))
- {
- CancelMovements(entity);
- }
- }
- private static IEnumerable<StockMovement> GetMovements(IStore store, Guid jriID, Filter<StockMovement>? filter, Columns<StockMovement> columns)
- {
- return store.Provider
- .Query(
- Filter<StockMovement>.And(
- new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(jriID),
- filter),
- columns)
- .ToObjects<StockMovement>();
- }
- private static IEnumerable<StockMovement> GetNotIssued(IStore store, Guid jriID, Columns<StockMovement> columns)
- {
- return GetMovements(store, jriID, new Filter<StockMovement>(x => x.Type).IsNotEqualTo(StockMovementType.Issue), columns);
- }
- private static IEnumerable<StockMovement> GetIssued(IStore store, Guid jriID, Columns<StockMovement> columns)
- {
- return GetMovements(store, jriID, new Filter<StockMovement>(x => x.Type).IsEqualTo(StockMovementType.Issue), columns);
- }
- private void CancelMovements(JobRequisitionItem entity)
- {
- // Here, we care about *all* movements into or out of this requi. If stuff has been issued, it must be included,
- // since we cannot return issued stock back to general stock for the job.
- var movements = GetMovements(this, entity.ID, null,
- Columns.None<StockMovement>().Add(
- x => x.Product.ID,
- x => x.Style.ID,
- x => x.Job.ID,
- x => x.Location.ID,
- x => x.Units,
- x => x.Cost,
- x => x.OrderItem.ID)
- .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local));
- var newMovements = new List<StockMovement>();
- foreach(var movement in movements)
- {
- var from = movement.CreateMovement();
- from.Date = entity.Cancelled;
- from.Cost = movement.Cost;
- from.System = true;
- from.JobRequisitionItem.ID = entity.ID;
- from.OrderItem.ID = movement.OrderItem.ID;
- from.Notes = "Requisition item cancelled";
- var to = movement.CreateMovement();
- to.Date = entity.Cancelled;
- to.Cost = movement.Cost;
- to.System = true;
- to.Notes = "Requisition item cancelled";
- to.OrderItem.ID = movement.OrderItem.ID;
- to.Transaction = from.Transaction;
- if(movement.Units > 0)
- {
- // If this movement was an increase to reservation allocation, we create a transfer out of the reservation.
- from.Issued = movement.Units;
- to.Received = movement.Units;
- from.Type = StockMovementType.TransferOut;
- to.Type = StockMovementType.TransferIn;
- }
- else if(movement.Units < 0)
- {
- // If this movement was a decrease to reservation allocation, we create a transfer into the reservation.
- from.Received = -movement.Units;
- to.Issued = -movement.Units;
- from.Type = StockMovementType.TransferIn;
- to.Type = StockMovementType.TransferOut;
- }
- newMovements.Add(from);
- newMovements.Add(to);
- }
- if(newMovements.Count > 0)
- {
- var batch = new StockMovementBatch
- {
- Notes = "Requisition item cancelled."
- };
- FindSubStore<StockMovementBatch>().Save(batch, "");
- foreach(var mvt in newMovements)
- {
- mvt.Batch.ID = batch.ID;
- }
- FindSubStore<StockMovement>().Save(newMovements, "Requisition item cancelled.");
- }
- }
- public static Columns<JobRequisitionItem> StatusRequiredColumns()
- {
- return Columns.None<JobRequisitionItem>().Add(
- x => x.ID,
- x => x.Archived,
- x => x.Cancelled,
- x => x.OrderRequired,
- x => x.Status,
- x => x.Style.ID,
- x => x.Product.ID,
- x => x.Qty,
- x => x.Dimensions.Value,
- x => x.Notes);
- }
- /// <summary>
- /// Ensure that the columns of <paramref name="item"/> match <see cref="StatusRequiredColumns"/>.
- /// </summary>
- /// <param name="store"></param>
- /// <param name="item"></param>
- /// <returns></returns>
- public static bool CalculateStatus(IStore store, JobRequisitionItem item)
- {
- if (item.Archived != DateTime.MinValue)
- item.Status = JobRequisitionItemStatus.Archived;
- else if (item.Cancelled != DateTime.MinValue)
- item.Status = JobRequisitionItemStatus.Cancelled;
- else
- {
- // We don't care about that which has been issued, because we're just looking at how much was allocated.
- // If we cared about the issued movements as well, then after issuing a requi item, it would become unallocated.
- // However, we do include transfers out of this requi, since then the stuff ain't actually been allocated.
- var stockMovements = GetNotIssued(store, item.ID,
- Columns.None<StockMovement>().Add(x => x.Units)
- .Add(x => x.Style.ID)
- .Add(x=>x.Dimensions.Value));
- var styleTotal = 0.0;
- var total = 0.0;
- foreach (var mvt in stockMovements)
- {
- if (mvt.Style.ID == item.Style.ID)
- {
- styleTotal += mvt.Units * mvt.Dimensions.Value;
- }
- total += mvt.Units * mvt.Dimensions.Value;
- }
- var remStyle = (item.Qty * item.Dimensions.Value) - styleTotal;
- var remTotal = (item.Qty * item.Dimensions.Value) - total;
- if (remStyle <= 0)
- {
- // Now, we care about what's actually been issued.
- var issued = GetIssued(store, item.ID, Columns.None<StockMovement>().Add(x => x.Units).Add(x=>x.Dimensions.Value));
- // If everything has been issued, the issued total will be a negative value to balance the Qty.
- if(item.Qty + issued.Sum(x => x.Units * x.Dimensions.Value) <= 0)
- {
- item.Status = JobRequisitionItemStatus.Issued;
- }
- else
- {
- item.Status = JobRequisitionItemStatus.Allocated;
- }
- }
- else if (remTotal <= 0)
- {
- // Find all unreceived POItems for this guy that are treatments (i.e., wrong product ID).
- var jriPois = store.Provider.Query(
- new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.JobRequisitionItem.ID).IsEqualTo(item.ID)
- .And(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(DateTime.MinValue)
- .And(x => x.PurchaseOrderItem.Product.ID).IsNotEqualTo(item.Product.ID),
- Columns.None<JobRequisitionItemPurchaseOrderItem>().Add(x => x.ID));
- if (jriPois.Rows.Count > 0)
- item.Status = JobRequisitionItemStatus.TreatmentOnOrder;
- else
- item.Status = JobRequisitionItemStatus.TreatmentRequired;
- }
- else
- {
- // Find all unreceived POItems for this guy.
- var jriPois = store.Provider.Query(
- new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.JobRequisitionItem.ID).IsEqualTo(item.ID)
- .And(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(DateTime.MinValue),
- Columns.None<JobRequisitionItemPurchaseOrderItem>().Add(x => x.PurchaseOrderItem.Product.ID)
- .Add(x => x.PurchaseOrderItem.Qty)
- .Add(x=>x.PurchaseOrderItem.Dimensions.Value))
- .ToObjects<JobRequisitionItemPurchaseOrderItem>()
- .ToList();
- var stockOrders = jriPois.Where(x => x.PurchaseOrderItem.Product.ID == item.Product.ID).ToList();
- var treatmentOrders = jriPois.Where(x => x.PurchaseOrderItem.Product.ID != item.Product.ID).ToList();
- remTotal -= stockOrders.Sum(x => x.PurchaseOrderItem.Qty * x.PurchaseOrderItem.Dimensions.Value);
- if (remTotal <= 0)
- {
- if (stockOrders.Count > 0)
- item.Status = JobRequisitionItemStatus.OnOrder;
- else
- {
- // This should be impossible to reach. We are at this point because remTotal <= 0, but stockOrders was an empty list. Therefore
- // remTotal is was <= 0 before checking PurchaseOrderItems, but then we should be TreatmentRequired, as above.
- store.Logger.Send(LogType.Error, store.UserID, $"Internal assertion failed: there is enough stock, but we didn't reach the correct clause.");
- if (treatmentOrders.Count > 0)
- item.Status = JobRequisitionItemStatus.TreatmentOnOrder;
- else
- item.Status = JobRequisitionItemStatus.TreatmentRequired;
- }
- }
- else if (item.OrderRequired != DateTime.MinValue)
- item.Status = JobRequisitionItemStatus.OrderRequired;
- else
- {
- // Even after all the orders have come through, we still don't have enough. We must order more.
- item.Status = JobRequisitionItemStatus.NotChecked;
- }
- }
- }
- return item.HasOriginalValue(x => x.Status);
- }
- public static bool CalculateStatus(IStore store, Guid jobRequiItemID)
- {
- var item = store.Provider.Query(
- new Filter<JobRequisitionItem>(x => x.ID).IsEqualTo(jobRequiItemID),
- StatusRequiredColumns())
- .ToObjects<JobRequisitionItem>()
- .FirstOrDefault();
- if(item is null)
- {
- store.Logger.Send(LogType.Error, store.UserID, $"No {nameof(JobRequisitionItem)} with ID {jobRequiItemID}");
- return false;
- }
- return CalculateStatus(store, item);
- }
-
- public static void UpdateStatus(IStore store, Guid jobRequiItemID, JobRequisitionItemAction action)
- {
- store.Logger.Send(LogType.Information, "",
- $" ** Updating Requisition Item Status ({store.GetType().EntityName()}) -> {jobRequiItemID}");
- var item = store.Provider.Query(
- new Filter<JobRequisitionItem>(x => x.ID).IsEqualTo(jobRequiItemID),
- StatusRequiredColumns())
- .ToObjects<JobRequisitionItem>()
- .FirstOrDefault();
- if (item is null)
- {
- store.Logger.Send(LogType.Error, store.UserID, $"No {nameof(JobRequisitionItem)} with ID {jobRequiItemID}");
- }
- else
- {
- if (CalculateStatus(store, item))
- {
- if (action != JobRequisitionItemAction.None)
- item.Notes += $"{(!String.IsNullOrWhiteSpace(item.Notes) ? "\n" : "")}Status updated to {item.Status.ToString().SplitCamelCase()} because a {store.Type.EntityName().Split('.').Last().SplitCamelCase()} was {action.ToString().ToLower()}";
- store.Provider.Save(item);
- }
- }
- }
- }
|