using System; using System.Collections.Generic; using System.Linq; using InABox.Clients; using InABox.Core; namespace Comal.Classes { public static class StockHoldingExtensions { /// /// Create a new stock movement from an , copying across the "key" properties; /// that is, the job, product, style, location and dimensions. /// /// /// Also sets the to today. /// /// /// public static StockMovement CreateMovement(this IStockHolding holding) { var movement = new StockMovement(); movement.Date = DateTime.Now; movement.Job.ID = holding.Job.ID; movement.Job.Synchronise(holding.Job); movement.Product.ID = holding.Product.ID; movement.Product.Synchronise(holding.Product); movement.Style.ID = holding.Style.ID; movement.Style.Synchronise(holding.Style); movement.Location.ID = holding.Location.ID; movement.Location.Synchronise(holding.Location); movement.Dimensions.CopyFrom(holding.Dimensions); return movement; } public static List GroupMovements(IEnumerable movements) { var grouped = new List(); var toGroup = movements.AsList(); while (toGroup.Count > 0) { var first = toGroup.First(); var selected = toGroup.Where(x => x.IsEqualTo(first)).ToList(); var holding = grouped.FirstOrDefault(x => x.IsEqualTo(first)); if (holding == null) { holding = new StockHolding(); holding.Location.CopyFrom(first.Location); holding.Product.CopyFrom(first.Product); holding.Style.CopyFrom(first.Style); holding.Job.CopyFrom(first.Job); holding.Dimensions.CopyFrom(first.Dimensions); grouped.Add(holding); } holding.Recalculate(selected); toGroup.RemoveAll(x => selected.Any(s => s.ID == x.ID)); } return grouped; } public static bool IsEqualTo(this IStockHolding row1, IStockHolding row2) { return row1.Product.ID == row2.Product.ID && row1.Location.ID == row2.Location.ID && row1.Job.ID == row2.Job.ID && row1.Style.ID == row2.Style.ID && row1.Dimensions.Unit.ID == row2.Dimensions.Unit.ID && row1.Dimensions.Length.IsEffectivelyEqual(row2.Dimensions.Length) && row1.Dimensions.Width.IsEffectivelyEqual(row2.Dimensions.Width) && row1.Dimensions.Height.IsEffectivelyEqual(row2.Dimensions.Height) && row1.Dimensions.Quantity.IsEffectivelyEqual(row2.Dimensions.Quantity) && row1.Dimensions.Weight.IsEffectivelyEqual(row2.Dimensions.Weight); } public static bool IsEqualTo(this CoreRow row1, CoreRow row2) where T1 : IStockHolding where T2 : IStockHolding { return row1.Get(x=>x.Product.ID) == row2.Get(x => x.Product.ID) && row1.Get(x=>x.Location.ID) == row2.Get(x => x.Location.ID) && row1.Get(x=>x.Job.ID) == row2.Get(x => x.Job.ID) && row1.Get(x=>x.Style.ID) == row2.Get(x => x.Style.ID) && row1.Get(x=>x.Dimensions.Unit.ID) == row2.Get(x => x.Dimensions.Unit.ID) && row1.Get(x=>x.Dimensions.Length).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Length)) && row1.Get(x=>x.Dimensions.Width).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Width)) && row1.Get(x=>x.Dimensions.Height).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Height)) && row1.Get(x=>x.Dimensions.Quantity).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Quantity)) && row1.Get(x=>x.Dimensions.Weight).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Weight)); } public static void Recalculate(this StockHolding holding, IEnumerable movements) { movements = movements.AsIList(); var units = movements.Sum(x => x.Units); var cost = movements.Select(x => x.Units * x.Cost).Sum(); var available = movements.Where(x => x.JobRequisitionItem.ID == Guid.Empty).Sum(x => x.Units); holding.Units = units; holding.Available = available; holding.Qty = movements.Sum(x => x.Units * x.Dimensions.Value); holding.Value = cost; holding.AverageValue = units.IsEffectivelyEqual(0.0F) ? 0.0d : cost / units; holding.Weight = holding.Qty * holding.Dimensions.Weight; } public static IEnumerable LoadRequisitionItems(this StockHolding holding, bool alwaysshowunallocated = false, Columns? columns = null) { columns ??= Columns.None(); columns.Add(x => x.ID) .Add(x => x.Product.ID) .Add(x => x.Style.ID) .AddDimensionsColumns(x => x.Dimensions) .Add(x => x.Job.ID) .Add(x => x.Job.JobNumber) .Add(x => x.Job.Name) .Add(x => x.Requisition.Number) .Add(x => x.Requisition.Description) .Add(x => x.Qty); var items = new Client().Query( new Filter(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID), columns) .ToObjects() .Where(x=>x.Product.ID == holding.Product.ID && x.Style.ID == holding.Style.ID && x.Dimensions.Equals(holding.Dimensions)); if (holding.Available > 0 || alwaysshowunallocated) { var requi = new JobRequisitionItem() { Qty = holding.Available }; requi.Requisition.Description = "Unallocated Items"; items = CoreUtils.One(requi).Concat(items); } return items; } public static IEnumerable AdjustValue(this StockHolding holding, double unitvalue, StockMovementBatch batch) { List _result = new List(); var movements = Client.Query( new Filter(x => x.Location.ID).IsEqualTo(holding.Location.ID) .And(x=>x.Product.ID).IsEqualTo(holding.Product.ID) .And(x => x.Style.ID).IsEqualTo(holding.Style.ID) .And(x => x.Dimensions).DimensionEquals(holding.Dimensions) .And(x => x.Job.ID).IsEqualTo(holding.Job.ID), Columns.Required().Add(x=>x.Units) ).Rows.ToObjects().ToArray(); var _allocations = movements.GroupBy(x => x.JobRequisitionItem.ID); foreach (var _allocation in _allocations) { var _units = _allocation.Sum(x => x.Units); if (!_units.IsEffectivelyEqual(0.0)) { var _transout = holding.CreateMovement(); _transout.Employee.ID = batch.Employee.ID; _transout.Issued = _units; _transout.Cost = holding.AverageValue; _transout.Type = StockMovementType.TransferOut; _transout.JobRequisitionItem.ID = _allocation.Key; _transout.Batch.ID = batch.ID; _transout.Notes = $"Adjusting Average Value from ${holding.AverageValue:F2} to ${unitvalue:F2}"; _result.Add(_transout); var _transin = holding.CreateMovement(); _transin.Date = _transout.Date.AddTicks(1); _transout.Employee.ID = batch.Employee.ID; _transin.Received = _units; _transin.Cost = unitvalue; _transin.Type = StockMovementType.TransferIn; _transin.Transaction = _transout.Transaction; _transin.JobRequisitionItem.ID = _allocation.Key; _transin.Batch.ID = batch.ID; _transin.Notes = $"Adjusting Average Value from ${holding.AverageValue:F2} to ${unitvalue:F2}"; _result.Add(_transin); } } return _result; } public static Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding> LoadStockHoldings(IEnumerable mvts, Columns columns, Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>? holdings = null, IQueryProvider? query = null) { query ??= Client.Provider; columns.Add(x => x.ID); columns.Add(x => x.Product.ID); columns.Add(x => x.Location.ID); columns.Add(x => x.Style.ID); columns.Add(x => x.Job.ID); columns.AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local); if(holdings != null) { mvts = mvts.Where(mvt => { var key = (mvt.Product.ID, mvt.Style.ID, mvt.Location.ID, mvt.Job.ID, mvt.Dimensions); return !holdings.ContainsKey(key); }).ToArray(); } else { holdings = new Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>(); } var productIDs = mvts.Select(x => x.Product.ID).Distinct().ToArray(); var locationIDs = mvts.Select(x => x.Location.ID).Distinct().ToArray(); var styleIDs = mvts.Select(x => x.Style.ID).Distinct().ToArray(); var jobIDs = mvts.Select(x => x.Job.ID).Distinct().ToArray(); var newHoldings = query.Query(new Filter(x => x.Product.ID).InList(productIDs) .And(x => x.Location.ID).InList(locationIDs) .And(x => x.Style.ID).InList(styleIDs) .And(x => x.Job.ID).InList(jobIDs), columns ).ToObjects(); foreach(var holding in newHoldings) { holdings[(holding.Product.ID, holding.Style.ID, holding.Location.ID, holding.Job.ID, holding.Dimensions)] = holding; } return holdings; } } }