فهرست منبع

Converted StockHolding to an AutoEntitySummary View
Removed "Recalculate" functionality for Stock Holdings
Removed manual updates to StockHoldings in StockMovementStore and StockMovementBatchStore

frankvandenbos 1 ماه پیش
والد
کامیت
149c55c727

+ 1 - 13
prs.classes/Entities/Product/Instance/ProductInstance.cs

@@ -101,18 +101,6 @@ namespace Comal.Classes
         [NullEditor]
         [NullEditor]
         [Obsolete("", true)]
         [Obsolete("", true)]
         public double Parameter { get; set; }
         public double Parameter { get; set; }
-
-        public double RecalculateAverageCost()
-        {
-            var movements = Client.Query(
-                new Filter<StockMovement>(x=>x.Product.ID).IsEqualTo(Product.ID)
-                    .And(x => x.Style.ID).IsEqualTo(Style.ID)
-                    .And(x => x.Dimensions).DimensionEquals(Dimensions)
-                    .And(x => x.Job.ID).IsEqualTo(Guid.Empty),
-                Columns.None<StockMovement>().Add(x=>x.Units).Add(x=>x.Cost)
-            ).Rows.ToObjects<StockMovement>().ToArray();
-            return movements.Average(x => x.Units * x.Cost);
-        }
-
+        
     }
     }
 }
 }

+ 20 - 312
prs.classes/Entities/Stock/StockHolding/StockHolding.cs

@@ -1,41 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
-using InABox.Clients;
+using System;
 using InABox.Core;
 using InABox.Core;
-using PRSClasses;
-
 
 
 namespace Comal.Classes
 namespace Comal.Classes
 {
 {
-    using HoldingDictionary = Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>;
-
-    public class StockHoldingLastStocktake : CoreAggregate<StockHolding, StockMovement, DateTime>
-    {
-        public override Expression<Func<StockMovement, DateTime>> Aggregate => x => x.Date;
-
-        public override Filter<StockMovement> Filter => new Filter<StockMovement>(x => x.Type)
-            .IsEqualTo(StockMovementType.StockTake);
-
-        public override Dictionary<Expression<Func<StockMovement, object?>>, Expression<Func<StockHolding, object?>>> Links =>
-            new Dictionary<Expression<Func<StockMovement, object?>>, Expression<Func<StockHolding, object?>>>()
-            {
-                { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID },
-                { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
-                { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
-                { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID },
-            }.AddRange(Dimensions.GetLinks<StockMovement, StockHolding>(x => x.Dimensions, x => x.Dimensions));
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Maximum;
-    }
-    
+    [AutoEntity(typeof(StockHoldingSummaryGenerator))]
     [UserTracking(typeof(StockMovement))]
     [UserTracking(typeof(StockMovement))]
-    [Unrecoverable]
     public class StockHolding : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>, 
     public class StockHolding : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>, 
         IStockHolding, ILicense<WarehouseLicense>
         IStockHolding, ILicense<WarehouseLicense>
     {
     {
-        
         [Editable(Editable.Disabled)]
         [Editable(Editable.Disabled)]
         [EditorSequence(1)]
         [EditorSequence(1)]
         public StockLocationLink Location { get; set; }
         public StockLocationLink Location { get; set; }
@@ -77,22 +49,31 @@ namespace Comal.Classes
         
         
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
         [EditorSequence(8)]
         [EditorSequence(8)]
-        public double Value { get; set; }
+        public double Available { get; set; }
         
         
-        [DoubleEditor(Editable = Editable.Disabled)]
+        [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
         [EditorSequence(9)]
         [EditorSequence(9)]
-        public double AverageValue { get; set; }
+        public double Allocated { get; set; }
         
         
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
         [EditorSequence(10)]
         [EditorSequence(10)]
-        public double Available { get; set; }
-
-        [Formula(typeof(StockHoldingAllocatedFormula))]
-        [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+        public double Value { get; set; }
+        
+        private class AverageValueFormula : ComplexFormulaGenerator<StockHolding, double>
+        {
+            public override IComplexFormulaNode<StockHolding, double> GetFormula() =>
+                Formula(
+                    FormulaOperator.Divide,
+                    Property(x => x.Value),
+                    Property(x => x.Units)
+                );
+        }
+        
+        [ComplexFormula(typeof(AverageValueFormula))]
+        [DoubleEditor(Editable = Editable.Disabled)]
         [EditorSequence(11)]
         [EditorSequence(11)]
-        public double Allocated { get; set; }
+        public double AverageValue { get; set; }
         
         
-        [Aggregate(typeof(StockHoldingLastStocktake))]
         [DateEditor(Editable = Editable.Disabled)]
         [DateEditor(Editable = Editable.Disabled)]
         [EditorSequence(11)]
         [EditorSequence(11)]
         public DateTime LastStockTake { get; set; }
         public DateTime LastStockTake { get; set; }
@@ -122,280 +103,7 @@ namespace Comal.Classes
         
         
             return filter.Combine();
             return filter.Combine();
         }
         }
-    }
-
-    internal class StockHoldingAllocatedFormula : IFormula<StockHolding, double>
-    {
-        public Expression<Func<StockHolding, double>> Value => x => x.Qty;
-
-        public FormulaOperator Operator => FormulaOperator.Subtract;
-
-        public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[]
-        {
-            x => x.Available
-        };
-
-        public FormulaType Type => FormulaType.Virtual;
-    }
-
-    public static class StockHoldingExtensions
-    {
-        /// <summary>
-        /// Create a new stock movement from an <see cref="IStockHolding"/>, copying across the "key" properties;
-        /// that is, the job, product, style, location and dimensions.
-        /// </summary>
-        /// <remarks>
-        /// Also sets the <see cref="StockMovement.Date"/> to today.
-        /// </remarks>
-        /// <param name="holding"></param>
-        /// <returns></returns>
-        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<StockHolding> GroupMovements(IEnumerable<StockMovement> movements)
-        {
-            var grouped = new List<StockHolding>();
-
-            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<T1,T2>(this CoreRow row1, CoreRow row2)
-            where T1 : IStockHolding
-            where T2 : IStockHolding
-        {
-            return row1.Get<T1,Guid>(x=>x.Product.ID) == row2.Get<T2,Guid>(x => x.Product.ID)
-                && row1.Get<T1,Guid>(x=>x.Location.ID) == row2.Get<T2,Guid>(x => x.Location.ID)
-                && row1.Get<T1,Guid>(x=>x.Job.ID) == row2.Get<T2,Guid>(x => x.Job.ID)
-                && row1.Get<T1,Guid>(x=>x.Style.ID) == row2.Get<T2,Guid>(x => x.Style.ID)
-                && row1.Get<T1,Guid>(x=>x.Dimensions.Unit.ID) == row2.Get<T2,Guid>(x => x.Dimensions.Unit.ID)
-                && row1.Get<T1,double>(x=>x.Dimensions.Length).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Length))
-                && row1.Get<T1,double>(x=>x.Dimensions.Width).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Width))
-                && row1.Get<T1,double>(x=>x.Dimensions.Height).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Height))
-                && row1.Get<T1,double>(x=>x.Dimensions.Quantity).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Quantity))
-                && row1.Get<T1,double>(x=>x.Dimensions.Weight).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Weight));
-        }
-
-        public static void Recalculate(this StockHolding holding, IEnumerable<StockMovement> 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<JobRequisitionItem> LoadRequisitionItems(this StockHolding holding, bool alwaysshowunallocated = false, Columns<JobRequisitionItem>? columns = null)
-        {
-            columns ??= Columns.None<JobRequisitionItem>();
-            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<JobRequisitionItem>().Query(
-                    new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
-                    columns)
-                .ToObjects<JobRequisitionItem>()
-                .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<StockMovement> AdjustValue(this StockHolding holding, double unitvalue, StockMovementBatch batch)
-        {
-            List<StockMovement> _result = new List<StockMovement>();
-            var movements = Client.Query(
-                new Filter<StockMovement>(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<StockMovement>().Add(x=>x.Units)
-            ).Rows.ToObjects<StockMovement>().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 HoldingDictionary LoadStockHoldings(IEnumerable<IStockHolding> mvts, Columns<StockHolding> columns, HoldingDictionary? holdings = null, IQueryProvider<StockHolding>? query = null)
-        {
-            query ??= Client<StockHolding>.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 HoldingDictionary();
-            }
-
-            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<StockHolding>(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<StockHolding>();
-            foreach(var holding in newHoldings)
-            {
-                holdings[(holding.Product.ID, holding.Style.ID, holding.Location.ID, holding.Job.ID, holding.Dimensions)] = holding;
-            }
-
-            return holdings;
-        }
-        
-    //     public static IEnumerable<Tuple<Guid,double>> GetAllocations(this StockHolding holding, bool alwaysshowunallocated)
-    //     {
-    //         var table = new Client<StockMovement>().Query(
-    //             StockHolding.GetFilter(holding),
-    //             new Columns<StockMovement>(x => x.Units)
-    //                 .Add(x => x.Location.ID)
-    //                 .Add(x => x.Product.ID)
-    //                 .Add(x => x.Style.ID)
-    //                 .AddDimensionsColumns(x => x.Dimensions)
-    //                 .Add(x => x.Cost)
-    //                 .Add(x => x.OrderItem.ID)
-    //                 .Add(x => x.JobRequisitionItem.ID)
-    //             );
-    //         
-    //         var movements = table
-    //             .ToObjects<StockMovement>();
-    //         
-    //         var groups = movements
-    //             .GroupBy(x => new
-    //             {
-    //                 Location = x.Location.ID,
-    //                 Product = x.Product.ID,
-    //                 Style = x.Style.ID,
-    //                 x.Dimensions,
-    //                 x.Cost,
-    //                 OrderItem = x.OrderItem.ID,
-    //                 JobRequisitionItem = x.JobRequisitionItem.ID
-    //             });
-    //
-    //         var result = groups
-    //             .Select(x => new Tuple<Guid, double>(
-    //                 x.Key.JobRequisitionItem,
-    //                 x.Sum(x => x.Units))
-    //             ).ToList();
-    //         
-    //         if (alwaysshowunallocated || !holding.Available.IsEffectivelyEqual(0))
-    //             result.Add(new Tuple<Guid, double>(Guid.Empty,holding.Available));
-    //         
-    //         return result;
-    //
-    //     }
-    
     }
     }
-    
 }
 }

+ 226 - 0
prs.classes/Entities/Stock/StockHolding/StockHoldingExtensions.cs

@@ -0,0 +1,226 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using InABox.Clients;
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public static class StockHoldingExtensions
+    {
+        /// <summary>
+        /// Create a new stock movement from an <see cref="IStockHolding"/>, copying across the "key" properties;
+        /// that is, the job, product, style, location and dimensions.
+        /// </summary>
+        /// <remarks>
+        /// Also sets the <see cref="StockMovement.Date"/> to today.
+        /// </remarks>
+        /// <param name="holding"></param>
+        /// <returns></returns>
+        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<StockHolding> GroupMovements(IEnumerable<StockMovement> movements)
+        {
+            var grouped = new List<StockHolding>();
+
+            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<T1,T2>(this CoreRow row1, CoreRow row2)
+            where T1 : IStockHolding
+            where T2 : IStockHolding
+        {
+            return row1.Get<T1,Guid>(x=>x.Product.ID) == row2.Get<T2,Guid>(x => x.Product.ID)
+                   && row1.Get<T1,Guid>(x=>x.Location.ID) == row2.Get<T2,Guid>(x => x.Location.ID)
+                   && row1.Get<T1,Guid>(x=>x.Job.ID) == row2.Get<T2,Guid>(x => x.Job.ID)
+                   && row1.Get<T1,Guid>(x=>x.Style.ID) == row2.Get<T2,Guid>(x => x.Style.ID)
+                   && row1.Get<T1,Guid>(x=>x.Dimensions.Unit.ID) == row2.Get<T2,Guid>(x => x.Dimensions.Unit.ID)
+                   && row1.Get<T1,double>(x=>x.Dimensions.Length).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Length))
+                   && row1.Get<T1,double>(x=>x.Dimensions.Width).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Width))
+                   && row1.Get<T1,double>(x=>x.Dimensions.Height).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Height))
+                   && row1.Get<T1,double>(x=>x.Dimensions.Quantity).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Quantity))
+                   && row1.Get<T1,double>(x=>x.Dimensions.Weight).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Weight));
+        }
+
+        public static void Recalculate(this StockHolding holding, IEnumerable<StockMovement> 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<JobRequisitionItem> LoadRequisitionItems(this StockHolding holding, bool alwaysshowunallocated = false, Columns<JobRequisitionItem>? columns = null)
+        {
+            columns ??= Columns.None<JobRequisitionItem>();
+            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<JobRequisitionItem>().Query(
+                    new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
+                    columns)
+                .ToObjects<JobRequisitionItem>()
+                .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<StockMovement> AdjustValue(this StockHolding holding, double unitvalue, StockMovementBatch batch)
+        {
+            List<StockMovement> _result = new List<StockMovement>();
+            var movements = Client.Query(
+                new Filter<StockMovement>(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<StockMovement>().Add(x=>x.Units)
+            ).Rows.ToObjects<StockMovement>().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<IStockHolding> mvts, Columns<StockHolding> columns, Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>? holdings = null, IQueryProvider<StockHolding>? query = null)
+        {
+            query ??= Client<StockHolding>.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<StockHolding>(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<StockHolding>();
+            foreach(var holding in newHoldings)
+            {
+                holdings[(holding.Product.ID, holding.Style.ID, holding.Location.ID, holding.Job.ID, holding.Dimensions)] = holding;
+            }
+
+            return holdings;
+        }
+        
+    }
+}

+ 140 - 0
prs.classes/Entities/Stock/StockHolding/StockHoldingSummaryGenerator.cs

@@ -0,0 +1,140 @@
+using System;
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class StockHoldingSummaryGenerator : AutoEntitySummaryGenerator<StockHolding, StockMovement>
+    {
+        public override void Configure()
+        {
+            GroupBy(x=>x.Location.ID, x=>x.Location.ID);
+            GroupBy(x=>x.Product.ID, x=>x.Product.ID);
+            GroupBy(x=>x.Style.ID, x=>x.Style.ID);
+            GroupBy(x=>x.Job.ID, x=>x.Job.ID);
+            GroupBy(x=>x.Dimensions.Unit.ID, x=>x.Dimensions.Unit.ID);
+            GroupBy(x=>x.Dimensions.Length, x=>x.Dimensions.Length);
+            GroupBy(x=>x.Dimensions.Width, x=>x.Dimensions.Width);
+            GroupBy(x=>x.Dimensions.Height, x=>x.Dimensions.Height);
+            GroupBy(x=>x.Dimensions.Weight, x=>x.Dimensions.Weight);
+            GroupBy(x=>x.Dimensions.Quantity,x=>x.Dimensions.Quantity);
+            GroupBy(x=>x.Dimensions.Value, x=>x.Dimensions.Value);
+            GroupBy(x=>x.Dimensions.UnitSize, x=>x.Dimensions.UnitSize);
+            
+            Aggregate(x => x.Units, AutoEntitySummaryAggregate.Sum, new UnitsFormula());
+            Aggregate(x => x.Qty, AutoEntitySummaryAggregate.Sum, new QtyFormula());
+            Aggregate(x=>x.Weight, AutoEntitySummaryAggregate.Sum, new WeightFormula());
+            Aggregate(x=>x.Available, AutoEntitySummaryAggregate.Sum, new AvailableStockFomula());
+            Aggregate(x=>x.Allocated, AutoEntitySummaryAggregate.Sum, new AllocatedStockFomula());
+            Aggregate(x=>x.Value, AutoEntitySummaryAggregate.Sum, new TotalCostFomula());
+            Aggregate(x =>x.LastStockTake, AutoEntitySummaryAggregate.Maximum, new StockTakeFormula());
+
+            Having(new Filter<StockHolding>(x => x.Units).IsNotEqualTo(FilterConstant.Zero)
+                .Or(x => x.Available).IsNotEqualTo(FilterConstant.Zero)
+                .Or(x => x.Allocated).IsNotEqualTo(FilterConstant.Zero)
+            );
+        }
+
+
+        
+        
+        private class UnitsFormula : ComplexFormulaGenerator<StockMovement, object?> 
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() => Formula(
+                FormulaOperator.Subtract,
+                Property(x => x.Received),
+                Property(x => x.Issued)
+            );
+        }
+        
+        private class QtyFormula : ComplexFormulaGenerator<StockMovement, object?> 
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() => Formula(
+                FormulaOperator.Multiply,
+                Formula(
+                    FormulaOperator.Subtract,
+                        Property(x => x.Received),
+                        Property(x => x.Issued)
+                    ),
+                Property(x=>x.Dimensions.Value)
+            );
+        }
+        
+        private class WeightFormula : ComplexFormulaGenerator<StockMovement, object?> 
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() => Formula(
+                FormulaOperator.Multiply,
+                Formula(
+                    FormulaOperator.Subtract,
+                    Property(x => x.Received),
+                    Property(x => x.Issued)
+                ),
+                Property(x=>x.Dimensions.Weight)
+            );
+        }
+        
+        private class StockTakeFormula : ComplexFormulaGenerator<StockMovement, object?> 
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() => 
+                If<StockMovement,StockMovementType,object?>(
+                    Property<StockMovement,StockMovementType>(x=>x.Type),
+                    Condition.Equals,
+                    Constant<StockMovement,StockMovementType>(StockMovementType.StockTake)
+                ).Then(
+                    Property(x=>x.Date)
+                ).Else(
+                    Constant(DateTime.MinValue)
+                );
+            
+        }
+        
+        private class AvailableStockFomula : ComplexFormulaGenerator<StockMovement, object?>
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() =>
+                If<StockMovement,Guid,object?>(
+                    Property<StockMovement, Guid>(x => x.JobRequisitionItem.ID),
+                    Condition.Equals,
+                    Constant<StockMovement, Guid>(Guid.Empty)
+                ).Then(
+                    Formula<StockMovement, object?>(
+                        FormulaOperator.Subtract,
+                        Property(x => x.Received),
+                        Property(x => x.Issued)
+                    )
+                ).Else(
+                    Constant(0.0)
+                );
+        }
+        
+        private class AllocatedStockFomula : ComplexFormulaGenerator<StockMovement, object?>
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() =>
+                If<StockMovement,Guid,object?>(
+                    Property<StockMovement, Guid>(x => x.JobRequisitionItem.ID),
+                    Condition.NotEqual,
+                    Constant<StockMovement, Guid>(Guid.Empty)
+                ).Then(
+                    Formula<StockMovement, object?>(
+                        FormulaOperator.Subtract,
+                        Property(x => x.Received),
+                        Property(x => x.Issued)
+                    )
+                ).Else(
+                    Constant(0.0)
+                );
+        }
+        
+        private class TotalCostFomula : ComplexFormulaGenerator<StockMovement, object?>
+        {
+            public override IComplexFormulaNode<StockMovement, object?> GetFormula() =>
+                Formula(FormulaOperator.Multiply,
+                    Formula<StockMovement, object?>(
+                        FormulaOperator.Subtract,
+                        Property(x => x.Received),
+                        Property(x => x.Issued)
+                    ),
+                    Property(x => x.Cost)
+                );
+        }
+        
+    }
+}

+ 136 - 0
prs.classes/Entities/Stock/StockHolding/StockHolding_Old.cs

@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+using InABox.Core;
+using PRSClasses;
+
+
+namespace Comal.Classes
+{
+    // public class StockHoldingLastStocktake : CoreAggregate<StockHolding, StockMovement, DateTime>
+    // {
+    //     public override Expression<Func<StockMovement, DateTime>> Aggregate => x => x.Date;
+    //
+    //     public override Filter<StockMovement> Filter => new Filter<StockMovement>(x => x.Type)
+    //         .IsEqualTo(StockMovementType.StockTake);
+    //
+    //     public override Dictionary<Expression<Func<StockMovement, object?>>, Expression<Func<StockHolding, object?>>> Links =>
+    //         new Dictionary<Expression<Func<StockMovement, object?>>, Expression<Func<StockHolding, object?>>>()
+    //         {
+    //             { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID },
+    //             { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
+    //             { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
+    //             { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID },
+    //         }.AddRange(Dimensions.GetLinks<StockMovement, StockHolding>(x => x.Dimensions, x => x.Dimensions));
+    //
+    //     public override AggregateCalculation Calculation => AggregateCalculation.Maximum;
+    // }
+    //
+    // [UserTracking(typeof(StockMovement))]
+    // [Unrecoverable]
+    // public class StockHolding : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>, 
+    //     IStockHolding, ILicense<WarehouseLicense>
+    // {
+    //     
+    //     [Editable(Editable.Disabled)]
+    //     [EditorSequence(1)]
+    //     public StockLocationLink Location { get; set; }
+    //     
+    //     private class ProductLookupGenerator : LookupDefinitionGenerator<Product, StockHolding>
+    //     {
+    //         public override Filter<Product>? DefineFilter(StockHolding[] items)
+    //             => LookupFactory.DefineFilter<Product>().And(x => x.NonStock).IsEqualTo(false);
+    //     }
+    //     [Editable(Editable.Disabled)]
+    //     [EditorSequence(2)]
+    //     [LookupDefinition(typeof(ProductLookupGenerator))]
+    //     public override ProductLink Product { get; set; }
+    //     
+    //     [DimensionsEditor(typeof(StockDimensions))]
+    //     [Editable(Editable.Disabled)]
+    //     [EditorSequence(3)]
+    //     public override StockDimensions Dimensions { get; set; }
+    //
+    //     [Editable(Editable.Disabled)]
+    //     [EditorSequence(4)]
+    //     public ProductStyleLink Style { get; set; }
+    //     
+    //     [Editable(Editable.Disabled)]
+    //     [EditorSequence(4)]
+    //     public JobLink Job { get; set; }
+    //     
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     [EditorSequence(5)]
+    //     public double Units { get; set; }
+    //     
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     [EditorSequence(6)]
+    //     public double Qty { get; set; }
+    //     
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     [EditorSequence(7)]
+    //     public double Weight { get; set; }
+    //     
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     [EditorSequence(8)]
+    //     public double Value { get; set; }
+    //     
+    //     [DoubleEditor(Editable = Editable.Disabled)]
+    //     [EditorSequence(9)]
+    //     public double AverageValue { get; set; }
+    //     
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     [EditorSequence(10)]
+    //     public double Available { get; set; }
+    //
+    //     [Formula(typeof(StockHoldingAllocatedFormula))]
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     [EditorSequence(11)]
+    //     public double Allocated { get; set; }
+    //     
+    //     [Aggregate(typeof(StockHoldingLastStocktake))]
+    //     [DateEditor(Editable = Editable.Disabled)]
+    //     [EditorSequence(11)]
+    //     public DateTime LastStockTake { get; set; }
+    //     
+    //     public static Column<IStockHolding>[] Columns => new Column<IStockHolding>[]
+    //     {
+    //         new Column<IStockHolding>(x => x.Job.ID),
+    //         new Column<IStockHolding>(x => x.Location.ID),
+    //         new Column<IStockHolding>(x => x.Product.ID),
+    //         new Column<IStockHolding>(x => x.Style.ID),
+    //         new Column<IStockHolding>(x => x.Dimensions.Unit.ID),
+    //         new Column<IStockHolding>(x => x.Dimensions.Quantity),
+    //         new Column<IStockHolding>(x => x.Dimensions.Length),
+    //         new Column<IStockHolding>(x => x.Dimensions.Width),
+    //         new Column<IStockHolding>(x => x.Dimensions.Height),
+    //         new Column<IStockHolding>(x => x.Dimensions.Weight),
+    //     };
+    //     
+    //     public static Filter<StockMovement>? GetFilter(IStockHolding holding)
+    //     {
+    //         var filter = new Filters<StockMovement>();
+    //     
+    //         foreach(var column in Columns)
+    //         {
+    //             filter.Add(new Filter<StockMovement>(column.Cast<StockMovement>()).IsEqualTo(CoreUtils.GetPropertyValue(holding, column.Property)));
+    //         }
+    //     
+    //         return filter.Combine();
+    //     }
+    // }
+    //
+    // internal class StockHoldingAllocatedFormula : IFormula<StockHolding, double>
+    // {
+    //     public Expression<Func<StockHolding, double>> Value => x => x.Qty;
+    //
+    //     public FormulaOperator Operator => FormulaOperator.Subtract;
+    //
+    //     public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[]
+    //     {
+    //         x => x.Available
+    //     };
+    //
+    //     public FormulaType Type => FormulaType.Virtual;
+    // }
+}

+ 10 - 10
prs.desktop/Panels/Products/Locations/StockHoldingGrid.cs

@@ -37,7 +37,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
     //Button ReserveButton = null;
     //Button ReserveButton = null;
 
 
     private Button TransferButton;
     private Button TransferButton;
-    private Button RecalculateButton;
+    //private Button RecalculateButton;
     private Button AdjustValueButton;
     private Button AdjustValueButton;
     private Button RelocateButton;
     private Button RelocateButton;
 
 
@@ -72,8 +72,8 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
             DynamicGridButtonPosition.Right);
             DynamicGridButtonPosition.Right);
         AdjustValueButton.Margin = new Thickness(AdjustValueButton.Margin.Left, AdjustValueButton.Margin.Top, 10, AdjustValueButton.Margin.Bottom);
         AdjustValueButton.Margin = new Thickness(AdjustValueButton.Margin.Left, AdjustValueButton.Margin.Top, 10, AdjustValueButton.Margin.Bottom);
         
         
-        RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings,
-            DynamicGridButtonPosition.Right);
+        //RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings,
+        //    DynamicGridButtonPosition.Right);
 
 
         HiddenColumns.Add(x => x.Product.ID);
         HiddenColumns.Add(x => x.Product.ID);
         HiddenColumns.Add(x => x.Job.ID);
         HiddenColumns.Add(x => x.Job.ID);
@@ -163,12 +163,12 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
         return false;
         return false;
     }
     }
 
 
-    private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
-    {
-        var result = StockUtils.RecalculateHoldingsOfLocations([Location.ID], out var _report);
-        MessageWindow.ShowMessage(String.Join("\n", _report ),"Recalculate");
-        return result;
-    }
+    // private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
+    // {
+    //     var result = StockUtils.RecalculateHoldingsOfLocations([Location.ID], out var _report);
+    //     MessageWindow.ShowMessage(String.Join("\n", _report ),"Recalculate");
+    //     return result;
+    // }
     
     
     public override DynamicGridColumns GenerateColumns()
     public override DynamicGridColumns GenerateColumns()
     {
     {
@@ -631,7 +631,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
             x.Get<StockHolding, double>(c => c.Dimensions.Value))
             x.Get<StockHolding, double>(c => c.Dimensions.Value))
         );
         );
         AdjustValueButton.IsEnabled = Location != null && Location.ID != Guid.Empty && _groups?.Count() == 1;
         AdjustValueButton.IsEnabled = Location != null && Location.ID != Guid.Empty && _groups?.Count() == 1;
-        RecalculateButton.IsEnabled = Location != null && Location.ID != Guid.Empty;
+        //RecalculateButton.IsEnabled = Location != null && Location.ID != Guid.Empty;
     }
     }
 
 
     protected override void Reload(
     protected override void Reload(

+ 12 - 12
prs.desktop/Panels/Products/Locations/StockLocationPanel.xaml.cs

@@ -34,20 +34,20 @@ namespace PRSDesktop
             ProductSetupActions.Standard(host);
             ProductSetupActions.Standard(host);
             //host.CreatePanelAction(new PanelAction
             //host.CreatePanelAction(new PanelAction
             //    { Caption = "Treament PO", Image = PRSDesktop.Resources.purchase, OnExecute = DoCreatePurchaseOrder });
             //    { Caption = "Treament PO", Image = PRSDesktop.Resources.purchase, OnExecute = DoCreatePurchaseOrder });
-            host.CreatePanelAction(new PanelAction
-                { Caption = "Recalculate Holdings", Image = PRSDesktop.Resources.certificate, OnExecute = DoRecalculate });
+            // host.CreatePanelAction(new PanelAction
+            //     { Caption = "Recalculate Holdings", Image = PRSDesktop.Resources.certificate, OnExecute = DoRecalculate });
         }
         }
 
 
-        private void DoRecalculate(PanelAction obj)
-        {
-            if (MessageWindow.ShowYesNo("Recalculate selected locations?", "Confirm"))
-            {
-                var ids = Locations.ExtractValues(x => x.ID, Selection.Selected).ToArray();
-                if (StockUtils.RecalculateHoldingsOfLocations(ids, out var _report))
-                    Refresh();
-                MessageWindow.ShowMessage(String.Join("\n",_report ),"Recalculate");
-            }
-        }
+        // private void DoRecalculate(PanelAction obj)
+        // {
+        //     if (MessageWindow.ShowYesNo("Recalculate selected locations?", "Confirm"))
+        //     {
+        //         var ids = Locations.ExtractValues(x => x.ID, Selection.Selected).ToArray();
+        //         if (StockUtils.RecalculateHoldingsOfLocations(ids, out var _report))
+        //             Refresh();
+        //         MessageWindow.ShowMessage(String.Join("\n",_report ),"Recalculate");
+        //     }
+        // }
 
 
         public string SectionName => "Stock Locations";
         public string SectionName => "Stock Locations";
 
 

+ 10 - 28
prs.desktop/Panels/Products/Master List/ProductHoldingControl.cs

@@ -18,7 +18,7 @@ public class ProductHoldingControl : DynamicDataGrid<StockHolding>, IProductCont
     
     
     private Button AdjustValueButton;
     private Button AdjustValueButton;
     
     
-    private Button RecalculateButton;
+    //private Button RecalculateButton;
     
     
     public ProductHoldingControl()
     public ProductHoldingControl()
     {
     {
@@ -56,8 +56,8 @@ public class ProductHoldingControl : DynamicDataGrid<StockHolding>, IProductCont
             DynamicGridButtonPosition.Right);
             DynamicGridButtonPosition.Right);
         AdjustValueButton.Margin = new Thickness(AdjustValueButton.Margin.Left, AdjustValueButton.Margin.Top, 10, AdjustValueButton.Margin.Bottom);
         AdjustValueButton.Margin = new Thickness(AdjustValueButton.Margin.Left, AdjustValueButton.Margin.Top, 10, AdjustValueButton.Margin.Bottom);
         
         
-        RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings,
-            DynamicGridButtonPosition.Right);
+        // RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings,
+        //     DynamicGridButtonPosition.Right);
         
         
     }
     }
     
     
@@ -108,7 +108,7 @@ public class ProductHoldingControl : DynamicDataGrid<StockHolding>, IProductCont
         );
         );
         AdjustValueButton.IsEnabled = (Product?.ID ?? Guid.Empty) != Guid.Empty && _groups?.Count() == 1;
         AdjustValueButton.IsEnabled = (Product?.ID ?? Guid.Empty) != Guid.Empty && _groups?.Count() == 1;
         
         
-        RecalculateButton.IsEnabled = (Product?.ID ?? Guid.Empty) != Guid.Empty;
+        //RecalculateButton.IsEnabled = (Product?.ID ?? Guid.Empty) != Guid.Empty;
     }
     }
 
 
     
     
@@ -141,24 +141,6 @@ public class ProductHoldingControl : DynamicDataGrid<StockHolding>, IProductCont
                     progress.Report("Saving Movements");
                     progress.Report("Saving Movements");
                     Client.Save(_updates,"Stock value adjusted from Products List");
                     Client.Save(_updates,"Stock value adjusted from Products List");
                     
                     
-                    // progress.Report("Updating Product Instances");
-                    // var instance = Client.Query(
-                    //     new Filter<ProductInstance>(x => x.Product.ID).IsEqualTo(_holding.Product.ID)
-                    //         .And(x => x.Style.ID).IsEqualTo(_holding.Style.ID)
-                    //         .And(x => x.Dimensions).IsEqualTo(_holding.Dimensions),
-                    //     Columns.Required<ProductInstance>().Add(x=>x.AverageCost)
-                    // ).Rows.FirstOrDefault()?.ToObject<ProductInstance>();
-                    // if (instance != null)
-                    // {
-                    //     var newinstanceaverage = instance.RecalculateAverageCost();
-                    //     if (!newinstanceaverage.IsEffectivelyEqual(instance.AverageCost))
-                    //     {
-                    //         instance.AverageCost = newinstanceaverage;
-                    //         Client.Save(instance,"Average Cost Updated by Products List");
-                    //     }
-                    // }
-                    
-                    
                 }
                 }
                 
                 
 
 
@@ -170,10 +152,10 @@ public class ProductHoldingControl : DynamicDataGrid<StockHolding>, IProductCont
         return false;
         return false;
     }
     }
     
     
-    private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
-    {
-        var result = StockUtils.RecalculateHoldingsOfProducts([Product.ID], out var messages);
-        MessageWindow.ShowMessage(string.Join("\n", messages), "Recalculate");
-        return result;
-    }
+    // private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
+    // {
+    //     var result = StockUtils.RecalculateHoldingsOfProducts([Product.ID], out var messages);
+    //     MessageWindow.ShowMessage(string.Join("\n", messages), "Recalculate");
+    //     return result;
+    // }
 }
 }

+ 134 - 133
prs.desktop/Utils/StockUtils.cs

@@ -11,138 +11,139 @@ namespace PRSDesktop;
 
 
 public static class StockUtils
 public static class StockUtils
 {
 {
-    public static bool RecalculateHoldingsOfLocations(Guid[] locationIDs, out List<String> report)
-    {
-        return RecalculateHoldings(new Filter<IStockHolding>(x => x.Location.ID).InList(locationIDs), out report);
-    }
-    public static bool RecalculateHoldingsOfProducts(Guid[] productIDs, out List<String> report)
-    {
-        return RecalculateHoldings(new Filter<IStockHolding>(x => x.Product.ID).InList(productIDs), out report);
-    }
-
-    public static bool RecalculateHoldings(Filter<IStockHolding> filter, out List<String> report)
-    {
-        bool result = false;
-        Dictionary<String, int> messages = new();
-
-        void AddMessage(String type)
-        {
-            messages.TryGetValue(type, out int count);
-            messages[type] = ++count;
-        }
-
-        Progress.ShowModal("Recalculating", progress =>
-        {
-            progress.Report("Loading Data");
-            MultiQuery query = new MultiQuery();
-
-            query.Add(
-                filter.Cast<StockHolding>(),
-                Columns.Required<StockHolding>().Add(x => x.ID)
-                    .Add(x => x.Location.ID)
-                    .Add(x => x.Product.ID)
-                    .Add(x => x.Job.ID)
-                    .Add(x => x.Style.ID)
-                    .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
-                    .Add(x => x.Units)
-                    .Add(x => x.AverageValue)
-                    .Add(x => x.Available)
-                    .Add(x => x.Qty)
-                    .Add(x => x.Weight)
-                    .Add(x => x.Value)
-            );
-
-            query.Add(
-                filter.Cast<StockMovement>(),
-                Columns.None<StockMovement>().Add(x => x.ID)
-                    .Add(x=>x.Location.ID)
-                    .Add(x => x.Product.ID)
-                    .Add(x => x.Job.ID)
-                    .Add(x => x.Style.ID)
-                    .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
-                    .Add(x => x.Units)
-                    .Add(x => x.Cost)
-                    .Add(x => x.JobRequisitionItem.ID)
-            );
-            query.Query();
-
-            var holdings = query.Get<StockHolding>();
-            var toDelete = new List<StockHolding>();
-            var movements = query.Get<StockMovement>();
-
-            double movementcount = (double)movements.Rows.Count;
-            progress.Report("Processing");
-            var updates = new List<StockHolding>();
-            
-            while (movements.Rows.Any())
-            {
-                double percent = ((movementcount - movements.Rows.Count) / movementcount) * 100.0;
-                progress.Report($"Processing ({percent:F2}% complete)");
-                var first = movements.Rows.First();
-                var selected = movements.Rows.Where(x => x.IsEqualTo<StockMovement, StockMovement>(first)).ToList();
-
-                var holdingrow = holdings.Rows.FirstOrDefault(x => x.IsEqualTo<StockHolding, StockMovement>(first));
-                StockHolding holding = null;
-                if (holdingrow != null)
-                {
-                    holdings.Rows.Remove(holdingrow);
-                    holding = holdingrow.ToObject<StockHolding>();
-                }
-
-                if (holding == null)
-                {
-                    var firstmovement = first.ToObject<StockMovement>();
-                    holding = new StockHolding();
-                    holding.Location.ID = firstmovement.Location.ID;
-                    holding.Product.ID = firstmovement.Product.ID;
-                    holding.Style.ID = firstmovement.Style.ID;
-                    holding.Job.ID = firstmovement.Job.ID;
-                    holding.Dimensions.CopyFrom(firstmovement.Dimensions);
-                }
-
-                holding.Recalculate(selected.ToObjects<StockMovement>());
-
-                if (holding.Units.IsEffectivelyEqual(0.0) && holding.Available.IsEffectivelyEqual(0.0))
-                {
-                    if (holding.ID != Guid.Empty)
-                        toDelete.Add(holding);
-                }
-                else if (holding.IsChanged())
-                {
-                    AddMessage(holding.ID != Guid.Empty ? "updated" : "added");
-                    updates.Add(holding);
-                }
-
-                foreach (var row in selected)
-                    movements.Rows.Remove(row);
-            }
-
-            toDelete.AddRange(holdings.Rows.ToObjects<StockHolding>());
-            foreach (var holding in toDelete)
-                AddMessage("deleted");
-
-            if (updates.Any())
-            {
-                result = true;
-                progress.Report($"Updating {updates.Count} Holdings");
-                new Client<StockHolding>().Save(updates.Where(x => x.IsChanged()), "Updated by Recalculation");
-            }
-
-            if (toDelete.Any())
-            {
-                result = true;
-                progress.Report($"Deleting {toDelete.Count} Holdings");
-                new Client<StockHolding>().Delete(toDelete, "Removed by Recalculation");
-            }
-
-        });
-        report = new List<String>();
-        if (messages.Any())
-            report.AddRange(messages.Select(x => $"{x.Value} holdings {x.Key}"));
-        else
-            report.Add("Nothing to Update");
-
-        return result;
-    }
+    // public static bool RecalculateHoldingsOfLocations(Guid[] locationIDs, out List<String> report)
+    // {
+    //     return RecalculateHoldings(new Filter<IStockHolding>(x => x.Location.ID).InList(locationIDs), out report);
+    // }
+    
+    // public static bool RecalculateHoldingsOfProducts(Guid[] productIDs, out List<String> report)
+    // {
+    //     return RecalculateHoldings(new Filter<IStockHolding>(x => x.Product.ID).InList(productIDs), out report);
+    // }
+
+    // public static bool RecalculateHoldings(Filter<IStockHolding> filter, out List<String> report)
+    // {
+    //     bool result = false;
+    //     Dictionary<String, int> messages = new();
+    //
+    //     void AddMessage(String type)
+    //     {
+    //         messages.TryGetValue(type, out int count);
+    //         messages[type] = ++count;
+    //     }
+    //
+    //     Progress.ShowModal("Recalculating", progress =>
+    //     {
+    //         progress.Report("Loading Data");
+    //         MultiQuery query = new MultiQuery();
+    //
+    //         query.Add(
+    //             filter.Cast<StockHolding>(),
+    //             Columns.Required<StockHolding>().Add(x => x.ID)
+    //                 .Add(x => x.Location.ID)
+    //                 .Add(x => x.Product.ID)
+    //                 .Add(x => x.Job.ID)
+    //                 .Add(x => x.Style.ID)
+    //                 .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
+    //                 .Add(x => x.Units)
+    //                 .Add(x => x.AverageValue)
+    //                 .Add(x => x.Available)
+    //                 .Add(x => x.Qty)
+    //                 .Add(x => x.Weight)
+    //                 .Add(x => x.Value)
+    //         );
+    //
+    //         query.Add(
+    //             filter.Cast<StockMovement>(),
+    //             Columns.None<StockMovement>().Add(x => x.ID)
+    //                 .Add(x=>x.Location.ID)
+    //                 .Add(x => x.Product.ID)
+    //                 .Add(x => x.Job.ID)
+    //                 .Add(x => x.Style.ID)
+    //                 .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
+    //                 .Add(x => x.Units)
+    //                 .Add(x => x.Cost)
+    //                 .Add(x => x.JobRequisitionItem.ID)
+    //         );
+    //         query.Query();
+    //
+    //         var holdings = query.Get<StockHolding>();
+    //         var toDelete = new List<StockHolding>();
+    //         var movements = query.Get<StockMovement>();
+    //
+    //         double movementcount = (double)movements.Rows.Count;
+    //         progress.Report("Processing");
+    //         var updates = new List<StockHolding>();
+    //         
+    //         while (movements.Rows.Any())
+    //         {
+    //             double percent = ((movementcount - movements.Rows.Count) / movementcount) * 100.0;
+    //             progress.Report($"Processing ({percent:F2}% complete)");
+    //             var first = movements.Rows.First();
+    //             var selected = movements.Rows.Where(x => x.IsEqualTo<StockMovement, StockMovement>(first)).ToList();
+    //
+    //             var holdingrow = holdings.Rows.FirstOrDefault(x => x.IsEqualTo<StockHolding, StockMovement>(first));
+    //             StockHolding holding = null;
+    //             if (holdingrow != null)
+    //             {
+    //                 holdings.Rows.Remove(holdingrow);
+    //                 holding = holdingrow.ToObject<StockHolding>();
+    //             }
+    //
+    //             if (holding == null)
+    //             {
+    //                 var firstmovement = first.ToObject<StockMovement>();
+    //                 holding = new StockHolding();
+    //                 holding.Location.ID = firstmovement.Location.ID;
+    //                 holding.Product.ID = firstmovement.Product.ID;
+    //                 holding.Style.ID = firstmovement.Style.ID;
+    //                 holding.Job.ID = firstmovement.Job.ID;
+    //                 holding.Dimensions.CopyFrom(firstmovement.Dimensions);
+    //             }
+    //
+    //             holding.Recalculate(selected.ToObjects<StockMovement>());
+    //
+    //             if (holding.Units.IsEffectivelyEqual(0.0) && holding.Available.IsEffectivelyEqual(0.0))
+    //             {
+    //                 if (holding.ID != Guid.Empty)
+    //                     toDelete.Add(holding);
+    //             }
+    //             else if (holding.IsChanged())
+    //             {
+    //                 AddMessage(holding.ID != Guid.Empty ? "updated" : "added");
+    //                 updates.Add(holding);
+    //             }
+    //
+    //             foreach (var row in selected)
+    //                 movements.Rows.Remove(row);
+    //         }
+    //
+    //         toDelete.AddRange(holdings.Rows.ToObjects<StockHolding>());
+    //         foreach (var holding in toDelete)
+    //             AddMessage("deleted");
+    //
+    //         if (updates.Any())
+    //         {
+    //             result = true;
+    //             progress.Report($"Updating {updates.Count} Holdings");
+    //             new Client<StockHolding>().Save(updates.Where(x => x.IsChanged()), "Updated by Recalculation");
+    //         }
+    //
+    //         if (toDelete.Any())
+    //         {
+    //             result = true;
+    //             progress.Report($"Deleting {toDelete.Count} Holdings");
+    //             new Client<StockHolding>().Delete(toDelete, "Removed by Recalculation");
+    //         }
+    //
+    //     });
+    //     report = new List<String>();
+    //     if (messages.Any())
+    //         report.AddRange(messages.Select(x => $"{x.Value} holdings {x.Key}"));
+    //     else
+    //         report.Add("Nothing to Update");
+    //
+    //     return result;
+    // }
 
 
 }
 }

+ 159 - 159
prs.stores/StockHoldingStore.cs

@@ -7,171 +7,171 @@ using MathNet.Numerics;
 
 
 namespace Comal.Stores;
 namespace Comal.Stores;
 
 
-using HoldingDictionary = Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>;
+// using HoldingDictionary = Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>;
 
 
 public class StockHoldingStore : BaseStore<StockHolding>
 public class StockHoldingStore : BaseStore<StockHolding>
 {
 {
     
     
-    public enum Action
-    {
-        Increase,
-        Decrease
-    }
+    // 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 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 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);
+    //         if(!holdings.TryGetValue(key, out var holding))
+    //         {
+    //             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;
+    //         }
+    //
+    //         var 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 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 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);
+    // }
 
 
-    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);
-            if(!holdings.TryGetValue(key, out var holding))
-            {
-                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;
-            }
-
-            var 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);
-    }
+    // /// <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);
+    // }
     
     
 }
 }

+ 17 - 18
prs.stores/StockMovementBatchStore.cs

@@ -9,23 +9,22 @@ namespace PRSStores;
 
 
 public class StockMovementBatchStore : BaseStore<StockMovementBatch>
 public class StockMovementBatchStore : BaseStore<StockMovementBatch>
 {
 {
-    protected override void BeforeDelete(StockMovementBatch entity)
-    {
-        base.BeforeDelete(entity);
+    // protected override void BeforeDelete(StockMovementBatch entity)
+    // {
+    //     base.BeforeDelete(entity);
+    //     //UpdateStockHoldings(entity);
+    // }
 
 
-        UpdateStockHoldings(entity);
-    }
-
-    private void UpdateStockHoldings(StockMovementBatch entity)
-    {
-        var movements = Provider.Query(
-            new Filter<StockMovement>(x => x.Batch.ID).IsEqualTo(entity.ID),
-            Columns.None<StockMovement>().Add(x => x.ID));
-        foreach(var row in movements.Rows)
-        {
-            // We need to do this in before delete, because aotherwise we wont have the data
-            // that we need to pull to properly update the stockholdings
-            StockHoldingStore.UpdateStockHolding(this, row.Get<StockMovement, Guid>(x => x.ID), StockHoldingStore.Action.Decrease);
-        }
-    }
+    // private void UpdateStockHoldings(StockMovementBatch entity)
+    // {
+    //     var movements = Provider.Query(
+    //         new Filter<StockMovement>(x => x.Batch.ID).IsEqualTo(entity.ID),
+    //         Columns.None<StockMovement>().Add(x => x.ID));
+    //     foreach(var row in movements.Rows)
+    //     {
+    //         // We need to do this in before delete, because aotherwise we wont have the data
+    //         // that we need to pull to properly update the stockholdings
+    //         StockHoldingStore.UpdateStockHolding(this, row.Get<StockMovement, Guid>(x => x.ID), StockHoldingStore.Action.Decrease);
+    //     }
+    // }
 }
 }

+ 43 - 43
prs.stores/StockMovementStore.cs

@@ -14,9 +14,9 @@ using HoldingDictionary = Dictionary<(Guid product, Guid style, Guid location, G
 public class StockMovementStore : BaseStore<StockMovement>
 public class StockMovementStore : BaseStore<StockMovement>
 {
 {
     // These will be initialised in BeforeSave
     // These will be initialised in BeforeSave
-    HoldingDictionary holdingData = null!;
-    StockMovement[] mvtData = null!;
-    HashSet<Guid> currentIDs = new();
+    //HoldingDictionary holdingData = null!;
+    //StockMovement[] mvtData = null!;
+    //HashSet<Guid> currentIDs = new();
 
 
     protected override void BeforeSave(IEnumerable<StockMovement> entities)
     protected override void BeforeSave(IEnumerable<StockMovement> entities)
     {
     {
@@ -26,13 +26,13 @@ public class StockMovementStore : BaseStore<StockMovement>
             base.BeforeSave(entity);
             base.BeforeSave(entity);
         }
         }
 
 
-        currentIDs = entities.Select(x => x.ID).Where(x => x != Guid.Empty).ToHashSet();
+        //currentIDs = entities.Select(x => x.ID).Where(x => x != Guid.Empty).ToHashSet();
 
 
-        // We load the movements according to the data currently in the database (i.e, before we've saved). This is to decrease the old holdings.
-        mvtData = StockHoldingStore.LoadMovementData(this, currentIDs.ToArray());
-        holdingData = StockHoldingStore.LoadStockHoldings(this, mvtData);
-
-        StockHoldingStore.ModifyHoldings(mvtData, holdingData, StockHoldingStore.Action.Decrease);
+        // // We load the movements according to the data currently in the database (i.e, before we've saved). This is to decrease the old holdings.
+        // mvtData = StockHoldingStore.LoadMovementData(this, currentIDs.ToArray());
+        // holdingData = StockHoldingStore.LoadStockHoldings(this, mvtData);
+        //
+        // StockHoldingStore.ModifyHoldings(mvtData, holdingData, StockHoldingStore.Action.Decrease);
 
 
         foreach(var sm in entities)
         foreach(var sm in entities)
         {
         {
@@ -72,37 +72,37 @@ public class StockMovementStore : BaseStore<StockMovement>
 
 
     protected override void AfterSave(IEnumerable<StockMovement> entities)
     protected override void AfterSave(IEnumerable<StockMovement> entities)
     {
     {
-        // Update the old movement data with the changes we've made.
-        foreach(var mvt in entities)
-        {
-            var dataMvt = mvtData.FirstOrDefault(x => x.ID == mvt.ID);
-            if (dataMvt is null) continue;
-
-            // Need to stop observing, because otherwise the order of property setting could and would break things.
-            dataMvt.SetObserving(false);
-            foreach(var (key, value) in mvt.OriginalValueList)
-            {
-                if(DatabaseSchema.Property(typeof(StockMovement), key) is IProperty prop)
-                {
-                    prop.Setter()(dataMvt, prop.Getter()(mvt));
-                }
-            }
-            dataMvt.SetObserving(true);
-        }
-
-        // Find all movements that weren't loaded in BeforeSave - i.e., they are new stock movements.
-        var newIDs = entities.Select(x => x.ID).Where(x => !currentIDs.Contains(x)).ToArray();
-        // Grab the data for these movements.
-        var newMvts = StockHoldingStore.LoadMovementData(this, newIDs);
-        // Load all the stock holdings for these movements, but only if we haven't loaded them already.
-        StockHoldingStore.LoadStockHoldings(this, newMvts, holdingData);
-
-        // Add the new movement data to our data. Note that the above line does the same for holdings.
-        mvtData = mvtData.Concatenate(newMvts);
-        
-        // Update the Relevant StockHolding with the details of this movement
-        StockHoldingStore.ModifyHoldings(mvtData, holdingData, StockHoldingStore.Action.Increase);
-        StockHoldingStore.SaveHoldings(this, holdingData);
+        // // Update the old movement data with the changes we've made.
+        // foreach(var mvt in entities)
+        // {
+        //     var dataMvt = mvtData.FirstOrDefault(x => x.ID == mvt.ID);
+        //     if (dataMvt is null) continue;
+        //
+        //     // Need to stop observing, because otherwise the order of property setting could and would break things.
+        //     dataMvt.SetObserving(false);
+        //     foreach(var (key, value) in mvt.OriginalValueList)
+        //     {
+        //         if(DatabaseSchema.Property(typeof(StockMovement), key) is IProperty prop)
+        //         {
+        //             prop.Setter()(dataMvt, prop.Getter()(mvt));
+        //         }
+        //     }
+        //     dataMvt.SetObserving(true);
+        // }
+
+        // // Find all movements that weren't loaded in BeforeSave - i.e., they are new stock movements.
+        // var newIDs = entities.Select(x => x.ID).Where(x => !currentIDs.Contains(x)).ToArray();
+        // // Grab the data for these movements.
+        // var newMvts = StockHoldingStore.LoadMovementData(this, newIDs);
+        // // Load all the stock holdings for these movements, but only if we haven't loaded them already.
+        // StockHoldingStore.LoadStockHoldings(this, newMvts, holdingData);
+        //
+        // // Add the new movement data to our data. Note that the above line does the same for holdings.
+        // mvtData = mvtData.Concatenate(newMvts);
+        //
+        // // Update the Relevant StockHolding with the details of this movement
+        // StockHoldingStore.ModifyHoldings(mvtData, holdingData, StockHoldingStore.Action.Increase);
+        // StockHoldingStore.SaveHoldings(this, holdingData);
         
         
         foreach(var entity in entities)
         foreach(var entity in entities)
         {
         {
@@ -120,9 +120,9 @@ public class StockMovementStore : BaseStore<StockMovement>
     {
     {
         base.BeforeDelete(entity);
         base.BeforeDelete(entity);
         
         
-        // We need to do this in before delete, because otherwise we wont have
-        // the data that we need to pull to properly update the stockholdings
-        StockHoldingStore.UpdateStockHolding(this, entity.ID,StockHoldingStore.Action.Decrease);
+        // // We need to do this in before delete, because otherwise we wont have
+        // // the data that we need to pull to properly update the stockholdings
+        // StockHoldingStore.UpdateStockHolding(this, entity.ID,StockHoldingStore.Action.Decrease);
         
         
         // Now let's pull the requisition ID, so that we can clean this up
         // Now let's pull the requisition ID, so that we can clean this up
         // properly in AfterDelete()
         // properly in AfterDelete()