123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419 |
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using Comal.Classes;
- using InABox.Core;
- using System;
- using NPOI.Util;
- namespace Comal.Stores
- {
- internal class RequisitionStore : BaseStore<Requisition>
- {
- private readonly bool _debug = true;
- private void Log(string format, params object[] values)
- {
- if (_debug)
- Logger.Send(LogType.Information, UserID, string.Format("- RequisitionStore:" + format, values));
- }
- private bool NeedsUpdating<TObject, T>(Requisition entity, TObject obj, Expression<Func<TObject, T>> property)
- where TObject : BaseObject
- {
- // If this is a new Requisition, we don't need to do anything
- if (entity.HasOriginalValue(x => x.ID))
- {
- var originalid = entity.GetOriginalValue(x => x.ID);
- if (originalid == Guid.Empty)
- {
- Log("NeedsUpdating() return false - original id is empty");
- return false;
- }
- }
- // if the Property has not changed, we don't need to do anything
- if (!obj.HasOriginalValue(property))
- {
- Log("NeedsUpdating() return false - {0} has not changed", property.ToString());
- return false;
- }
- return true;
- }
- private bool LoadRequisitionItems(Requisition entity, ref IList<RequisitionItem> requisitionitems)
- {
- requisitionitems ??= Provider.Query(
- new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID).And(x=>x.ActualQuantity).IsNotEqualTo(0.0),
- Columns.None<RequisitionItem>().Add(x => x.ID)
- .Add(x => x.Description)
- .Add(x => x.Quantity)
- .Add(x => x.Code)
- .Add(x => x.Location.ID)
- .Add(x => x.Style.ID)
- .Add(x => x.Product.ID)
- .Add(x => x.Product.NonStock)
- .Add(x => x.SourceJRI.ID)
- .Add(x => x.JobRequisitionItem.ID)
- .Add(x =>x.JobLink.ID)
- .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.All)
- .Add(x => x.ActualQuantity)
- .AddSubColumns(x => x.Charge, Columns.All<ActualCharge>())
- .Add(x => x.JobScope.ID)
- .AddDimensionsColumns(x => x.Product.DefaultInstance.Dimensions, Dimensions.ColumnsType.All)
- .Add(x => x.Cost)
- ).ToList<RequisitionItem>();
- return requisitionitems.Any();
- }
- private bool LoadDeliveryItems(Requisition entity, ref IList<DeliveryItem> deliveryitems)
- {
- deliveryitems ??= Provider.Query(
- new Filter<DeliveryItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID),
- Columns.None<DeliveryItem>().Add(x => x.ID, x => x.DeliveredDate)
- ).ToList<DeliveryItem>();
- return deliveryitems.Any();
- }
- #region TakenBy
- private void UpdateTakenBy(Requisition entity, ref IList<RequisitionItem> items, ref IList<DeliveryItem> deliveryitems)
- {
- Log("UpdateTakenBy() - starting");
- if (!NeedsUpdating(entity, entity.TakenBy, x => x.ID))
- return;
- if (!LoadDeliveryItems(entity, ref deliveryitems))
- {
- Log("UpdateTakenBy() - no delivery items to update");
- return;
- }
- foreach (var deliveryitem in deliveryitems)
- if (entity.TakenBy.IsValid() && deliveryitem.DeliveredDate.IsEmpty())
- {
- Log("UpdateTakenBy() - Setting DeliveryDate");
- deliveryitem.DeliveredDate = DateTime.Now;
- }
- else if (!entity.TakenBy.IsValid() && !deliveryitem.DeliveredDate.IsEmpty())
- {
- Log("UpdateTakenBy() - Clearing DeliveryDate");
- deliveryitem.DeliveredDate = DateTime.MinValue;
- }
- var updates = deliveryitems.Where(x => x.IsChanged());
- if (updates.Any())
- FindSubStore<DeliveryItem>().Save(updates,
- entity.TakenBy.IsValid() ? "Requisition taken by " + entity.TakenBy.Code : "Requisition [TakenBy] has been cleared");
- Log("UpdateTakenBy() - done");
- }
- #endregion
- protected override void BeforeSave(Requisition entity)
- {
- base.BeforeSave(entity);
- if (entity.TakenBy.IsValid() || entity.Delivery.Completed != DateTime.MinValue || entity.Delivery.Delivered != DateTime.MinValue)
- {
- if (entity.Archived.IsEmpty())
- entity.Archived = DateTime.Now;
- }
- else
- {
- if (!entity.Archived.IsEmpty())
- entity.Archived = DateTime.MinValue;
- }
- }
- protected override void AfterSave(Requisition entity)
- {
- base.AfterSave(entity);
- IList<RequisitionItem> requisitionitems = null;
- IList<DeliveryItem> deliveryitems = null;
-
- UpdateDeliveryItems(entity, ref requisitionitems, ref deliveryitems);
- UpdateTakenBy(entity, ref requisitionitems, ref deliveryitems);
- UpdateStockBatches(entity, ref requisitionitems);
- UpdateTrackingKanban<RequisitionKanban, Requisition, RequisitionLink>(entity, e =>
- {
- if (!entity.Archived.Equals(DateTime.MinValue) || entity.TakenBy.IsValid())
- return KanbanStatus.Complete;
- if (entity.Delivery.IsValid())
- {
- if (entity.Delivery.Completed != DateTime.MinValue)
- {
- return KanbanStatus.Complete;
- }
- }
- if (!entity.Filled.Equals(DateTime.MinValue))
- return KanbanStatus.Waiting;
- if (Provider.Query(
- new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID),
- Columns.None<RequisitionItem>().Add(x => x.ID)
- ).Rows.Any()
- )
- return KanbanStatus.InProgress;
- return KanbanStatus.Open;
- });
- }
-
- protected override void BeforeDelete(Requisition entity)
- {
- UnlinkTrackingKanban<RequisitionKanban, Requisition, RequisitionLink>(entity);
- }
-
- #region Delivery Items
- private void CreateDeliveryItems(Requisition entity, ref IList<RequisitionItem> requisitionitems,
- ref IList<DeliveryItem> deliveryitems)
- {
- if (!LoadRequisitionItems(entity, ref requisitionitems))
- {
- Log("CreateDeliveryItems() - no requisition items to update");
- return;
- }
- var updates = new List<DeliveryItem>();
- foreach (var item in requisitionitems)
- updates.Add(item.CreateDeliveryItem(entity));
- if (updates.Any())
- FindSubStore<DeliveryItem>().Save(updates, "Requisition [Filled] flag has been set");
- deliveryitems = updates;
- }
- private void ClearDeliveryItems(Requisition entity, ref IList<DeliveryItem> deliveryitems)
- {
- if (!LoadDeliveryItems(entity, ref deliveryitems))
- {
- Log("ClearDeliveryItems() - no delivery items to update");
- return;
- }
- if (deliveryitems.Any())
- FindSubStore<DeliveryItem>().Delete(deliveryitems, "Requisition [Filled] flag has been cleared");
- deliveryitems = new List<DeliveryItem>();
- }
- private void UpdateDeliveryItems(Requisition entity, ref IList<RequisitionItem> requisitionitems,
- ref IList<DeliveryItem> deliveryitems)
- {
- Log("UpdateDeliveryItems() - starting");
- if (!NeedsUpdating(entity, entity, x => x.Filled))
- {
- Log("UpdateDeliveryItems() - NeedsUpdate() return false");
- return;
- }
- var oldfilled = entity.GetOriginalValue(x => x.Filled);
- var newfilled = entity.Filled;
- // Gone from Blank to Filled -> Create a Batch
- if (oldfilled.IsEmpty() && !newfilled.IsEmpty())
- {
- Log("UpdateDeliveryItems() - Filled has been set");
- ClearDeliveryItems(entity, ref deliveryitems);
- CreateDeliveryItems(entity, ref requisitionitems, ref deliveryitems);
- }
- // Gone from Filled to Blank -> Clear Out the Batch
- else if (newfilled.IsEmpty() && !oldfilled.IsEmpty())
- {
- Log("UpdateDeliveryItems() - Filled has been cleared");
- ClearDeliveryItems(entity, ref deliveryitems);
- }
- // Do nothing - filled flag has been updated, not set or cleared
- Log("UpdateDeliveryItems() - done");
- }
- #endregion
- #region StockMovements
- private StockMovement CreateStockMovement(IEmployee employee, DateTime date, IStockMovementBatch batch, IProduct product, IStockLocation location,
- IProductStyle style, IJob? job, IJobRequisitionItem? jri, IDimensions dimensions, Guid txnid, ActualCharge charge, JobScopeLink scope, bool system, string note)
- {
- var movement = new StockMovement();
- movement.Batch.ID = batch.ID;
- movement.Product.ID = product.ID;
- movement.Location.ID = location.ID;
- movement.Style.ID = style.ID;
- movement.Job.ID = job?.ID ?? Guid.Empty;
- movement.JobRequisitionItem.ID = jri?.ID ?? Guid.Empty;
- movement.Dimensions.CopyFrom(dimensions);
- movement.Charge.CopyFrom(charge);
- movement.JobScope.CopyFrom(scope);
- movement.System = system;
- movement.Transaction = txnid;
- movement.Notes = note;
- movement.Date = date;
- movement.Employee.ID = employee.ID;
- return movement;
- }
- private void CreateStockBatch(Requisition entity, ref IList<RequisitionItem> items)
- {
- if(!LoadRequisitionItems(entity, ref items))
- {
- Log("CreateStockBatch() - no items to update!");
- return;
- }
- var batch = new StockMovementBatch
- {
- Type = StockMovementBatchType.Issue,
- TimeStamp = entity.Filled,
- Notes = string.Format("Requisition #{0}", entity.Number)
- };
- batch.Employee.ID = entity.Employee.ID;
- batch.Requisition.ID = entity.ID;
- FindSubStore<StockMovementBatch>().Save(batch, "");
-
- var timestamp = entity.Filled;
- var updates = new List<StockMovement>();
- foreach (var item in items)
- {
- if (item.Product.NonStock) continue;
- var holdingQty = 0.0;
- var dimensions = item.Dimensions;
-
- if(item.JobLink.ID != Guid.Empty)
- {
- var holdings = Provider.Query<StockHolding>(
- new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(item.Location.ID)
- .And(x => x.Product.ID).IsEqualTo(item.Product.ID)
- .And(x => x.Style.ID).IsEqualTo(item.Style.ID)
- .And(x => x.Dimensions).DimensionEquals(item.Dimensions)
- .And(x => x.Job.ID).IsEqualTo(item.JobLink.ID),
- Columns.None<StockHolding>().Add(x => x.Units)
- );
- holdingQty = holdings.Rows.FirstOrDefault()?.Get<StockHolding, double>(x => x.Units) ?? 0.0;
- }
- var qty = item.ActualQuantity;
-
- var txnid = Guid.NewGuid();
- if (holdingQty.IsEffectivelyLessThan(qty))
- {
- // Don't pull more than the required quantity, meaning if the holding is negative, it will remain negative.
- var extraRequired = qty - Math.Max(0.0,holdingQty);
-
- // We're going to redirect this general stock direct to the target job, so reduce the amount required from the selected holding
- qty = holdingQty;
-
- // We don't have enough stock in this case, so transfer the necessary stock from general. We don't check for quantity in general stock,
- // but instead will let general stock go negative if not enough. Obviously we have the stock, because its being sent to site. So if we do
- // get negatives, it means probably our number are wrong I think.
- // Transfer the necessary balance from General Stock...
- var from = CreateStockMovement(entity.Employee, timestamp, batch, item.Product, item.Location, item.Style, null, null,
- dimensions, txnid, item.Charge, item.JobScope, true, $"Requisition #{entity.Number} Internal Transfer");
- from.Issued = extraRequired;
- from.Type = StockMovementType.TransferOut;
- from.Cost = item.Cost;
- timestamp = timestamp.AddTicks(1);
- // ... to the target job that we are issuing from.
- var to = CreateStockMovement(entity.Employee, timestamp, batch, item.Product, item.Location, item.Style, entity.JobLink, item.JobRequisitionItem, dimensions, txnid, item.Charge, item.JobScope, true,
- $"Requisition #{entity.Number} Internal Transfer");
- to.Received = extraRequired;
- to.Type = StockMovementType.TransferIn;
- to.Cost = item.Cost;
- timestamp = timestamp.AddTicks(1);
- updates.Add(from);
- updates.Add(to);
- // The remainder that wasn't in the holding has now been moved straight to the target job/JRI
- }
-
- // if we have to change either job or JRI
- if (!qty.IsEffectivelyEqual(0.0) && (entity.JobLink.ID != item.JobLink.ID || item.JobRequisitionItem.ID != item.SourceJRI.ID))
- {
- // Transfer from the item job to the requisition job
- var from = CreateStockMovement(entity.Employee, timestamp, batch, item.Product, item.Location, item.Style, item.JobLink, item.SourceJRI,
- dimensions, txnid, item.Charge, item.JobScope, true, $"Requisition #{entity.Number} Internal Transfer");
- from.Issued = qty;
- from.Type = StockMovementType.TransferOut;
- from.Cost = item.Cost;
- timestamp = timestamp.AddTicks(1);
- // ... to the job.
- var to = CreateStockMovement(entity.Employee, timestamp, batch, item.Product, item.Location, item.Style, entity.JobLink, item.JobRequisitionItem, dimensions, txnid, item.Charge, item.JobScope, true,
- $"Requisition #{entity.Number} Internal Transfer");
- to.Received = qty;
- to.Type = StockMovementType.TransferIn;
- to.Cost = item.Cost;
- timestamp = timestamp.AddTicks(1);
- updates.Add(from);
- updates.Add(to);
- }
- // Now we can issue to full original quantity to the entity job :-)
- var mvt = CreateStockMovement(entity.Employee, timestamp, batch, item.Product, item.Location, item.Style, entity.JobLink, item.JobRequisitionItem, dimensions, txnid, item.Charge, item.JobScope,
- false,
- $"Requisition #{entity.Number}");
- mvt.Issued = item.ActualQuantity;
- mvt.Type = StockMovementType.Issue;
- mvt.Cost = item.Cost;
- updates.Add(mvt);
-
- }
- FindSubStore<StockMovement>().Save(updates, "");
- }
- private void ClearStockBatch(Requisition entity)
- {
- Log("ClearStockBatch()");
- var batches = Provider.Query(
- new Filter<StockMovementBatch>(x => x.Requisition.ID).IsEqualTo(entity.ID),
- Columns.None<StockMovementBatch>().Add(x => x.ID)
- ).Rows.Select(x => x.ToObject<StockMovementBatch>());
- if (batches.Any())
- FindSubStore<StockMovementBatch>().Delete(batches, "");
- }
- private void UpdateStockBatches(Requisition entity, ref IList<RequisitionItem> items)
- {
- Log("UpdateStockBatch() - starting");
- if (!NeedsUpdating(entity, entity, x => x.StockUpdated))
- return;
- var oldupdate = entity.GetOriginalValue(x => x.StockUpdated);
- var newupdate = entity.StockUpdated;
- // Gone from Blank to Updated -> Create a Batch
- if (oldupdate.IsEmpty() && !newupdate.IsEmpty())
- {
- Log("UpdateStockBatch() - creating batch");
- ClearStockBatch(entity);
- CreateStockBatch(entity, ref items);
- }
- // Gone from Updated to Blank -> Clear Out the Batch
- else if (newupdate.IsEmpty() && !oldupdate.IsEmpty())
- {
- Log("UpdateStockBatch() - clearing batch");
- ClearStockBatch(entity);
- }
- // Do nothing - Updated flag has been updated, not set or cleared
- Log("UpdateStockBatch() - done");
- }
- #endregion
- }
- }
|