using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Comal.Classes; using InABox.Core; namespace Comal.Stores { internal class RequisitionStore : BaseStore { 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(Requisition entity, Expression> property) { // 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 (!entity.HasOriginalValue(property)) { Log("NeedsUpdating() return false - {0} has not changed", property.ToString()); return false; } return true; } private bool LoadRequisitionItems(Requisition entity, ref IList requisitionitems) { requisitionitems ??= Provider.Query( new Filter(x => x.RequisitionLink.ID).IsEqualTo(entity.ID) ).ToList(); return requisitionitems.Any(); } private bool LoadDeliveryItems(Requisition entity, ref IList deliveryitems) { deliveryitems ??= Provider.Query( new Filter(x => x.RequisitionLink.ID).IsEqualTo(entity.ID), new Columns(x => x.ID, x => x.DeliveredDate) ).ToList(); return deliveryitems.Any(); } #region TakenBy private void UpdateTakenBy(Requisition entity, ref IList items, ref IList deliveryitems) { Log("UpdateTakenBy() - starting"); if (!NeedsUpdating(entity, x => x.TakenBy)) 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().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 requisitionitems = null; IList deliveryitems = null; UpdateDeliveryItems(entity, ref requisitionitems, ref deliveryitems); UpdateTakenBy(entity, ref requisitionitems, ref deliveryitems); UpdateStockBatches(entity, ref requisitionitems); UpdateTrackingKanban(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(x => x.RequisitionLink.ID).IsEqualTo(entity.ID), new Columns(x => x.ID) ).Rows.Any() ) return KanbanStatus.InProgress; return KanbanStatus.Open; }); } protected override void BeforeDelete(Requisition entity) { UnlinkTrackingKanban(entity); } #region Delivery Items private void CreateDeliveryItems(Requisition entity, ref IList requisitionitems, ref IList deliveryitems) { if (!LoadRequisitionItems(entity, ref requisitionitems)) { Log("CreateDeliveryItems() - no requisition items to update"); return; } var updates = new List(); foreach (var item in requisitionitems) updates.Add(item.CreateDeliveryItem(entity)); if (updates.Any()) FindSubStore().Save(updates, "Requisition [Filled] flag has been set"); deliveryitems = updates; } private void ClearDeliveryItems(Requisition entity, ref IList deliveryitems) { if (!LoadDeliveryItems(entity, ref deliveryitems)) { Log("ClearDeliveryItems() - no delivery items to update"); return; } if (deliveryitems.Any()) FindSubStore().Delete(deliveryitems, "Requisition [Filled] flag has been cleared"); deliveryitems = new List(); } private void UpdateDeliveryItems(Requisition entity, ref IList requisitionitems, ref IList deliveryitems) { Log("UpdateDeliveryItems() - starting"); if (!NeedsUpdating(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(Guid employeeid, DateTime date, Guid batchid, Guid productid, Guid locationid, Guid styleid, Guid jobid, IDimensions dimensions, Guid txnid, bool system, string note) { var movement = new StockMovement(); movement.Batch.ID = batchid; movement.Product.ID = productid; movement.Location.ID = locationid; movement.Style.ID = styleid; movement.Job.ID = jobid; movement.Dimensions.CopyFrom(dimensions); movement.System = system; movement.Transaction = txnid; movement.Notes = note; movement.Date = date; movement.Employee.ID = employeeid; return movement; } private void CreateStockBatch(Requisition entity, ref IList 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().Save(batch, ""); var updates = new List(); foreach (var item in items) { var locationid = item.Location.ID; var productid = item.Product.ID; var styleid = item.Style.ID; var unitsize = item.Dimensions.UnitSize; var jobid = entity.JobLink.ID; var holdingQty = 0.0; if(jobid != Guid.Empty) { var holdings = Provider.Query( new Filter(x => x.Location.ID).IsEqualTo(locationid) .And(x => x.Product.ID).IsEqualTo(productid) .And(x => x.Style.ID).IsEqualTo(styleid) .And(x => x.Dimensions).DimensionEquals(item.Dimensions) .And(x => x.Job.ID).IsEqualTo(jobid) .And(x => x.Qty).IsGreaterThan(0.0), new Columns(x => x.Qty) ); holdingQty = holdings.Rows.FirstOrDefault()?.Get(x => x.Qty) ?? 0.0; } var qty = item.ActualQuantity; var dimensions = item.Product.DefaultInstance.Dimensions; var txnid = Guid.NewGuid(); if(holdingQty < qty) { // Don't pull more than the required quantity, meaning if the holding is negative, it will remain negative. var extraRequired = Math.Max(qty - holdingQty, qty); // 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.ID, entity.Filled, batch.ID, productid, locationid, styleid, Guid.Empty, dimensions, txnid, true, string.Format("Requisition #{0} Internal Transfer", entity.Number)); from.Issued = extraRequired; from.Type = StockMovementType.TransferOut; // ... to the job. var to = CreateStockMovement(entity.Employee.ID, entity.Filled, batch.ID, productid, locationid, styleid, jobid, dimensions, txnid, true, string.Format("Requisition #{0} Internal Transfer", entity.Number)); to.Received = extraRequired; to.Type = StockMovementType.TransferIn; updates.Add(from); updates.Add(to); // Now we have a full qty in the job holding, and we can issue to site. } var mvt = CreateStockMovement(entity.Employee.ID, entity.Filled, batch.ID, productid, locationid, styleid, jobid, dimensions, txnid, false, string.Format("Requisition #{0}", entity.Number)); mvt.Issued = qty; mvt.Type = StockMovementType.Issue; updates.Add(mvt); } FindSubStore().Save(updates, ""); } private void ClearStockBatch(Requisition entity) { Log("ClearStockBatch()"); var batches = Provider.Query( new Filter(x => x.Requisition.ID).IsEqualTo(entity.ID), new Columns(x => x.ID) ).Rows.Select(x => x.ToObject()); if (batches.Any()) FindSubStore().Delete(batches, ""); } private void UpdateStockBatches(Requisition entity, ref IList items) { Log("UpdateStockBatch() - starting"); if (!NeedsUpdating(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 } }