| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 | using Comal.Classes;using InABox.Core;using InABox.Database;using System.Linq;using System;using MathNet.Numerics;namespace Comal.Stores;using HoldingDictionary = Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>;public class StockHoldingStore : BaseStore<StockHolding>{    public enum Action    {        Increase,        Decrease    }    public static StockMovement[] LoadMovementData(IStore store, Guid[] ids)    {        return store.Provider.Query(            new Filter<StockMovement>(x => x.ID).InList(ids),            Columns.None<StockMovement>().Add(x => x.ID)                .Add(x => x.Location.ID)                .Add(x => x.Product.ID)                .Add(x => x.Style.ID)                .Add(x => x.Job.ID)                .Add(x => x.Dimensions.Unit.ID)                .Add(x => x.Dimensions.Quantity)                .Add(x => x.Dimensions.Height)                .Add(x => x.Dimensions.Width)                .Add(x => x.Dimensions.Length)                .Add(x => x.Dimensions.Weight)                .Add(x => x.Dimensions.UnitSize)                .Add(x => x.Dimensions.Value)                .Add(x => x.JobRequisitionItem.ID)                .Add(x => x.Units)                .Add(x => x.Cost)        ).ToArray<StockMovement>();    }    public static HoldingDictionary LoadStockHoldings(IStore store, StockMovement[] mvts, HoldingDictionary? holdings = null)    {        return StockHoldingExtensions.LoadStockHoldings(            mvts,            Columns.None<StockHolding>()                .Add(x => x.Units)                .Add(x => x.Qty)                .Add(x => x.Value)                .Add(x => x.Available)                .Add(x => x.Weight)                .Add(x => x.AverageValue),            holdings, store.QueryProvider<StockHolding>());    }    public static void ModifyHoldings(StockMovement[] mvts, HoldingDictionary holdings, Action action)    {        foreach(var mvt in mvts)        {            var key = (mvt.Product.ID, mvt.Style.ID, mvt.Location.ID, mvt.Job.ID, mvt.Dimensions);            var holding = holdings.GetValueOrDefault(key);            if(holding is null)            {                holding = new();                holding.Location.ID = mvt.Location.ID;                holding.Product.ID = mvt.Product.ID;                holding.Style.ID = mvt.Style.ID;                holding.Job.ID = mvt.Job.ID;                holding.Dimensions.CopyFrom(mvt.Dimensions);                holdings[key] = holding;            }            double multiplier = action == Action.Increase ? 1F : -1F;            holding.Units += (multiplier * mvt.Units);            holding.Qty += (multiplier * mvt.Units * mvt.Dimensions.Value);            holding.Value += (multiplier * mvt.Units * mvt.Cost);            holding.Available += (multiplier * (mvt.JobRequisitionItem.ID == Guid.Empty ? mvt.Units : 0.0));                        holding.Weight = holding.Qty * holding.Dimensions.Weight;            holding.AverageValue = holding.Units != 0 ? holding.Value / holding.Units : 0.0F;        }    }    public static void SaveHoldings(IStore store, HoldingDictionary holdings)    {        var holdingStore = store.FindSubStore<StockHolding>();        holdingStore.Delete(            holdings.Values.Where(x => x.ID != Guid.Empty && x.Units.IsEffectivelyEqual(0.0) && x.Available.IsEffectivelyEqual(0.0)), "");        holdingStore.Save(            holdings.Values.Where(x => x.IsChanged() && (!x.Units.IsEffectivelyEqual(0.0) || !x.Available.IsEffectivelyEqual(0.0))), "");    }    public static void UpdateStockHoldings(IStore store, Guid[] ids, Action action)    {        var movements = LoadMovementData(store, ids);        var holdings = LoadStockHoldings(store, movements);        ModifyHoldings(movements, holdings, action);        SaveHoldings(store, holdings);    }    /// <summary>    /// Maintains the Stock Holding Table when manipulating Stock Movements    /// We only accept an ID, because the rest of the movement is pulled from the database    /// (slower, but more reliable)    /// </summary>    /// <param name="id">The id of the Stock Movement to query</param>    /// <param name="action">The action to perform (increase / decrease)</param>    public static void UpdateStockHolding(IStore store, Guid id, Action action)    {        var movement = store.Provider.Query(            new Filter<StockMovement>(x => x.ID).IsEqualTo(id),            Columns.None<StockMovement>().Add(x => x.ID)                .Add(x => x.Location.ID)                .Add(x => x.Product.ID)                .Add(x => x.Style.ID)                .Add(x => x.Job.ID)                .Add(x => x.Dimensions.Unit.ID)                .Add(x => x.Dimensions.Quantity)                .Add(x => x.Dimensions.Height)                .Add(x => x.Dimensions.Width)                .Add(x => x.Dimensions.Length)                .Add(x => x.Dimensions.Weight)                .Add(x => x.Dimensions.UnitSize)                .Add(x => x.Dimensions.Value)                .Add(x => x.JobRequisitionItem.ID)                .Add(x => x.Units)                .Add(x => x.Cost)        ).Rows            .FirstOrDefault()?            .ToObject<StockMovement>();        if (movement == null)            return;                var holding = store.Provider.Query(new Filter<StockHolding>(x => x.Product.ID).IsEqualTo(movement.Product.ID)                .And(x => x.Location.ID).IsEqualTo(movement.Location.ID)                .And(x => x.Style.ID).IsEqualTo(movement.Style.ID)                .And(x => x.Job.ID).IsEqualTo(movement.Job.ID)                .And(x => x.Dimensions).DimensionEquals(movement.Dimensions),                Columns.None<StockHolding>().Add(x => x.ID)                    .Add(x => x.Units)                    .Add(x => x.Qty)                    .Add(x => x.Value)                    .Add(x => x.Available)            ).Rows            .FirstOrDefault()?            .ToObject<StockHolding>();        if (holding == null)        {            holding = new();            holding.Location.ID = movement.Location.ID;            holding.Product.ID = movement.Product.ID;            holding.Style.ID = movement.Style.ID;            holding.Job.ID = movement.Job.ID;            holding.Dimensions.CopyFrom(movement.Dimensions);        }        double multiplier = action == Action.Increase ? 1F : -1F;        holding.Units += (multiplier * movement.Units);        holding.Qty += (multiplier * movement.Units * movement.Dimensions.Value);        holding.Value += (multiplier * movement.Units * movement.Cost);        holding.Available += (multiplier * (movement.JobRequisitionItem.ID == Guid.Empty ? movement.Units : 0.0));                holding.Weight = holding.Qty * holding.Dimensions.Weight;        holding.AverageValue = holding.Units != 0 ? holding.Value / holding.Units : 0.0F;        // Automagically clean up empty holdings        if (holding.Units.IsEffectivelyEqual(0.0) && holding.Available.IsEffectivelyEqual(0.0))        {            if (holding.ID != Guid.Empty)                DbFactory.NewProvider(Logger.Main).Delete(holding, "");        }        else            DbFactory.NewProvider(Logger.Main).Save(holding);    }    }
 |