Ver código fonte

Converted StockHolding from View to Table; updated StockMovementStore to maintain StockHoldings

frogsoftware 1 ano atrás
pai
commit
bcb17b025b

+ 236 - 183
prs.classes/Entities/Stock/StockHolding.cs

@@ -7,215 +7,268 @@ using PRSClasses;
 namespace Comal.Classes
 {
     
-    public class StockHoldingUnitAggregate : CoreAggregate<StockHolding, StockMovement, double>
-    {
-        public override Expression<Func<StockMovement, double>> Aggregate => x => x.Units;
-
-        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.Job.ID, StockHolding => StockHolding.Job.ID },
-                { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
-                { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
-                { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
-                { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
-                { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
-                { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
-                { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
-                { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
-            };
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-    }
+    // public class StockHoldingUnitAggregate : CoreAggregate<StockHolding, StockMovement, double>
+    // {
+    //     public override Expression<Func<StockMovement, double>> Aggregate => x => x.Units;
+    //
+    //     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.Job.ID, StockHolding => StockHolding.Job.ID },
+    //             { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
+    //             { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
+    //             { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
+    //             { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
+    //             { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
+    //             { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
+    //             { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
+    //             { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
+    //         };
+    //
+    //     public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+    // }
+    //
+    // public class StockHoldingValueAggregate : CoreAggregate<StockHolding, StockMovement, double>
+    // {
+    //     public override Expression<Func<StockMovement, double>> Aggregate => x => x.Value;
+    //
+    //     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.Job.ID, StockHolding => StockHolding.Job.ID },
+    //             { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
+    //             { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
+    //             { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
+    //             { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
+    //             { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
+    //             { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
+    //             { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
+    //             { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
+    //         };
+    //
+    //     public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+    // }
+    //
+    // public class StockHoldingAverageValueFormula : IFormula<StockHolding, double>
+    // {
+    //     public Expression<Func<StockHolding, double>> Value => x => x.Value;
+    //
+    //     public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[] { x => x.Units };
+    //
+    //     public FormulaOperator Operator => FormulaOperator.Divide;
+    //     
+    //     public FormulaType Type => FormulaType.Virtual;
+    // }
+    //
+    // public class StockHoldingQuantityAggregate : CoreAggregate<StockHolding, StockMovement, double>
+    // {
+    //     public override Expression<Func<StockMovement, double>> Aggregate => x => x.Qty;
+    //
+    //     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.Job.ID, StockHolding => StockHolding.Job.ID },
+    //             { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
+    //             { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
+    //             { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
+    //             { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
+    //             { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
+    //             { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
+    //             { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
+    //             { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
+    //         };
+    //
+    //     public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+    // }
+
+
+    // public class StockHoldingAvailableAggregate : CoreAggregate<StockHolding, StockMovement, double>
+    // {
+    //     public override Expression<Func<StockMovement, double>> Aggregate => x => x.Units;
+    //
+    //     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.Job.ID, StockHolding => StockHolding.Job.ID },
+    //             { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
+    //             { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
+    //             { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
+    //             { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
+    //             { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
+    //             { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
+    //             { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
+    //             { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
+    //         };
+    //
+    //     public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+    //
+    //     public override Filter<StockMovement>? Filter => new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty);
+    // }
     
-    public class StockHoldingValueAggregate : CoreAggregate<StockHolding, StockMovement, double>
-    {
-        public override Expression<Func<StockMovement, double>> Aggregate => x => x.Value;
-
-        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.Job.ID, StockHolding => StockHolding.Job.ID },
-                { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
-                { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
-                { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
-                { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
-                { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
-                { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
-                { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
-                { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
-            };
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-    }
+    // public class StockHoldingWeightFormula : IFormula<StockHolding, double>
+    // {
+    //     public Expression<Func<StockHolding, double>> Value => x => x.Qty;
+    //
+    //     public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[] { x => x.Dimensions.Weight };
+    //
+    //     public FormulaOperator Operator => FormulaOperator.Multiply;
+    //     
+    //     public FormulaType Type => FormulaType.Virtual;
+    // }
+    //
+    // public class StockHoldingIsRemnantCondition : ICondition<StockHolding, double, object>
+    // {
+    //     public Expression<Func<StockHolding, double>> Left => x => x.Dimensions.Value;
+    //
+    //     public Condition Condition => Condition.LessThan;
+    //
+    //     public Expression<Func<StockHolding, double>> Right => x => x.Product.DefaultInstance.Dimensions.Value;
+    //
+    //     public Expression<Func<StockHolding, object>> True => x => true;
+    //
+    //     public Expression<Func<StockHolding, object>> False => x => null;
+    //     
+    //     public ConditionType Type => ConditionType.Virtual;
+    // }
     
-    public class StockHoldingAverageValueFormula : IFormula<StockHolding, double>
-    {
-        public Expression<Func<StockHolding, double>> Value => x => x.Value;
-
-        public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[] { x => x.Units };
-
-        public FormulaOperator Operator => FormulaOperator.Divide;
-        
-        public FormulaType Type => FormulaType.Virtual;
-    }
-
-    public class StockHoldingQuantityAggregate : CoreAggregate<StockHolding, StockMovement, double>
-    {
-        public override Expression<Func<StockMovement, double>> Aggregate => x => x.Qty;
-
-        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.Job.ID, StockHolding => StockHolding.Job.ID },
-                { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
-                { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
-                { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
-                { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
-                { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
-                { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
-                { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
-                { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
-            };
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-    }
-
-    public class StockHoldingAvailableAggregate : CoreAggregate<StockHolding, StockMovement, double>
-    {
-        public override Expression<Func<StockMovement, double>> Aggregate => x => x.Units;
-
-        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.Job.ID, StockHolding => StockHolding.Job.ID },
-                { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
-                { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
-                { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
-                { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
-                { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
-                { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
-                { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
-                { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
-            };
-
-        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
-
-        public override Filter<StockMovement>? Filter => new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty);
-    }
-    
-    public class StockHoldingWeightFormula : IFormula<StockHolding, double>
-    {
-        public Expression<Func<StockHolding, double>> Value => x => x.Qty;
-
-        public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[] { x => x.Dimensions.Weight };
-
-        public FormulaOperator Operator => FormulaOperator.Multiply;
-        
-        public FormulaType Type => FormulaType.Virtual;
-    }
-
-    public class StockHoldingIsRemnantCondition : ICondition<StockHolding, double, object>
-    {
-        public Expression<Func<StockHolding, double>> Left => x => x.Dimensions.Value;
-
-        public Condition Condition => Condition.LessThan;
-
-        public Expression<Func<StockHolding, double>> Right => x => x.Product.DefaultInstance.Dimensions.Value;
-
-        public Expression<Func<StockHolding, object>> True => x => true;
-
-        public Expression<Func<StockHolding, object>> False => x => null;
-        
-        public ConditionType Type => ConditionType.Virtual;
-    }
-    
-    public class StockHoldingUnionGenerator : AutoEntityUnionGenerator<IStockHolding>
-    {
-        protected override void Configure()
-        {
-            AddTable<StockMovement>();
-        }
-
-        public override bool Distinct => true;
-
-        public override Column<IStockHolding>[] IDColumns => Columns;
-
-        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();
-        }
-    }
+    // public class StockHoldingUnionGenerator : AutoEntityUnionGenerator<IStockHolding>
+    // {
+    //     protected override void Configure()
+    //     {
+    //         AddTable<StockMovement>();
+    //     }
+    //
+    //     public override bool Distinct => true;
+    //
+    //     public override Column<IStockHolding>[] IDColumns => Columns;
+    //
+    //     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();
+    //     }
+    // }
+    //
+    // [UserTracking(typeof(StockMovement))]
+    // [AutoEntity(typeof(StockHoldingUnionGenerator))]
+    // public class StockHoldingView : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>, 
+    //     IStockHolding, ILicense<WarehouseLicense>
+    // {
+    //     
+    //     public override ProductLink Product { get; set; }
+    //
+    //     public ProductStyleLink Style { get; set; }
+    //
+    //     public StockLocationLink Location { get; set; }
+    //
+    //     public JobLink Job { get; set; }
+    //
+    //     [RequiredColumn]
+    //     [DimensionsEditor(typeof(StockDimensions), AllowEditingUnit = false)]
+    //     public override StockDimensions Dimensions { get; set; }
+    //
+    //     [Condition(typeof(StockHoldingIsRemnantCondition))]
+    //     [NullEditor]
+    //     public bool IsRemnant { get; set; }
+    //     
+    //     [Aggregate(typeof(StockHoldingUnitAggregate))]
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     public double Units { get; set; }
+    //     
+    //     [Aggregate(typeof(StockHoldingQuantityAggregate))]
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     public double Qty { get; set; }
+    //     
+    //     [Formula(typeof(StockHoldingWeightFormula))]
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     public double Weight { get; set; }
+    //     
+    //     [Aggregate(typeof(StockHoldingValueAggregate))]
+    //     [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+    //     public double Value { get; set; }
+    //     
+    //     [Formula(typeof(StockHoldingAverageValueFormula))]
+    //     [DoubleEditor(Editable = Editable.Disabled)]
+    //     public double AverageValue { get; set; }
+    // }
     
+    // FV 6/2/24: This used to be a view (see above), but we were running into performance issues trying to
+    // get aggregates fof stock locations (ie show me how many holdings in a given location was taking upwrds of
+    // 20 seconds.  Have moved update logic to StockHoldingStore / StockMovementStore so updates are a bit slower,
+    // but reads are now much faster.
     [UserTracking(typeof(StockMovement))]
-    [AutoEntity(typeof(StockHoldingUnionGenerator))]
     public class StockHolding : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>, 
         IStockHolding, ILicense<WarehouseLicense>
     {
         
-        public override ProductLink Product { get; set; }
-
-        public ProductStyleLink Style { get; set; }
-
+        [Editable(Editable.Disabled)]
+        [EditorSequence(1)]
         public StockLocationLink Location { get; set; }
-
-        public JobLink Job { get; set; }
-
-        [RequiredColumn]
+        
+        [Editable(Editable.Disabled)]
+        [EditorSequence(2)]
+        public override ProductLink Product { get; set; }
+        
         [DimensionsEditor(typeof(StockDimensions), AllowEditingUnit = false)]
+        [Editable(Editable.Disabled)]
+        [EditorSequence(3)]
         public override StockDimensions Dimensions { get; set; }
 
-        [Condition(typeof(StockHoldingIsRemnantCondition))]
-        [NullEditor]
-        public bool IsRemnant { get; set; }
+        [Editable(Editable.Disabled)]
+        [EditorSequence(4)]
+        public ProductStyleLink Style { get; set; }
         
-        [Aggregate(typeof(StockHoldingUnitAggregate))]
-        [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
-        public double Units { get; set; }
+        [Editable(Editable.Disabled)]
+        [EditorSequence(4)]
+        public JobLink Job { get; set; }
         
-        [Aggregate(typeof(StockHoldingAvailableAggregate))]
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
-        public double Available { get; set; }
+        [EditorSequence(5)]
+        public double Units { get; set; }
         
-        [Aggregate(typeof(StockHoldingQuantityAggregate))]
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+        [EditorSequence(6)]
         public double Qty { get; set; }
         
-        [Formula(typeof(StockHoldingWeightFormula))]
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+        [EditorSequence(7)]
         public double Weight { get; set; }
         
-        [Aggregate(typeof(StockHoldingValueAggregate))]
         [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
+        [EditorSequence(8)]
         public double Value { get; set; }
         
-        [Formula(typeof(StockHoldingAverageValueFormula))]
         [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; }
     }
+    
 }

+ 3 - 3
prs.classes/Entities/Stock/StockLocation/StockLocation.cs

@@ -9,15 +9,15 @@ namespace Comal.Classes
     public class StockLocationHoldings : CoreAggregate<StockLocation, StockHolding, Guid>
     {
         public override Expression<Func<StockHolding, Guid>> Aggregate => x => x.Product.ID;
-
+    
         public override Filter<StockHolding> Filter => new Filter<StockHolding>(x=>x.Value).IsGreaterThan(0.000001).Or(x=>x.Value).IsLessThan(-0.000001);
-
+    
         public override Dictionary<Expression<Func<StockHolding, object>>, Expression<Func<StockLocation, object>>> Links =>
             new Dictionary<Expression<Func<StockHolding, object>>, Expression<Func<StockLocation, object>>>()
             {
                 { StockHolding => StockHolding.Location.ID, StockLocation => StockLocation.ID },
             };
-
+    
         public override AggregateCalculation Calculation => AggregateCalculation.Count;
     }
     

+ 1 - 0
prs.shared/Database Update Scripts/DatabaseUpdateScripts.cs

@@ -43,5 +43,6 @@ public static class DatabaseUpdateScripts
         DataUpdater.RegisterUpdateScript<Update_7_52>();
         DataUpdater.RegisterUpdateScript<Update_7_54>();
         DataUpdater.RegisterUpdateScript<Update_7_55>();
+        DataUpdater.RegisterUpdateScript<Update_7_56>();
     }
 }

+ 1 - 1
prs.shared/Database Update Scripts/Update_7_55.cs

@@ -41,4 +41,4 @@ internal class Update_7_55 : DatabaseUpdateScript
             }
         }
     }
-}
+}

+ 28 - 0
prs.shared/Database Update Scripts/Update_7_56.cs

@@ -0,0 +1,28 @@
+using Comal.Classes;
+using Comal.Stores;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared.Database_Update_Scripts;
+
+internal class Update_7_56 : DatabaseUpdateScript
+{
+    public override VersionNumber Version => new(7, 56);
+
+    public override bool Update()
+    {
+        var ids = DbFactory.Provider.Query<StockMovement>().Rows
+            .Select(r => r.Get<StockMovement, Guid>(x => x.ID)).ToArray();
+        int i = 0;
+        foreach (var id in ids)
+        {
+            StockHoldingStore.UpdateStockHolding(id, StockHoldingStore.Action.Increase);
+            i++;
+            if (i % 100 == 0)
+                Logger.Send(LogType.Information, "", $"Converted {i}/{ids.Length} Stock Holdings");
+        }
+        if (i % 100 != 0)
+            Logger.Send(LogType.Information, "", $"Converted {ids.Length}/{ids.Length} Stock Holdings");
+        return true;
+    }
+}

+ 3 - 0
prs.shared/PRS.Shared.csproj

@@ -25,6 +25,9 @@
       <AutoGen>True</AutoGen>
       <DependentUpon>Resources.resx</DependentUpon>
     </Compile>
+    <Compile Include="..\prs.stores\StockHoldingStore.cs">
+      <Link>prs.stores\StockHoldingStore.cs</Link>
+    </Compile>
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 0
prs.stores/PRSStores.projitems

@@ -77,6 +77,7 @@
     <Compile Include="$(MSBuildThisFileDirectory)SetoutStore.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)StagingSetoutStore.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)StockAreaStore.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)StockHoldingStore.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)StockLocationStore.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)StockMovementStore.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)StockWarehouseStore.cs" />

+ 88 - 0
prs.stores/StockHoldingStore.cs

@@ -0,0 +1,88 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+using System;
+using System.Linq;
+namespace Comal.Stores;
+
+public class StockHoldingStore : BaseStore<StockHolding>
+{
+
+    public enum Action
+    {
+        Increase,
+        Decrease
+    }
+
+    /// <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(Guid id, Action action)
+    {
+        var movement = DbFactory.Provider.Query(
+            new Filter<StockMovement>(x => x.ID).IsEqualTo(id),
+            new Columns<StockMovement>(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 = DbFactory.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.Unit.ID).IsEqualTo(movement.Dimensions.Unit.ID)
+                .And(x => x.Dimensions.UnitSize).IsEqualTo(movement.Dimensions.UnitSize)
+            ).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 (Math.Abs(holding.Units) < 0.000001F)
+        {
+            if (holding.ID != Guid.Empty)
+                DbFactory.Provider.Delete(holding, "");
+        }
+        else
+            DbFactory.Provider.Save(holding);
+    }
+    
+}

+ 22 - 1
prs.stores/StockMovementStore.cs

@@ -10,8 +10,22 @@ namespace PRSStores;
 
 public class StockMovementStore : BaseStore<StockMovement>
 {
+    protected override void BeforeSave(StockMovement sm)
+    {
+        base.BeforeSave(sm);
+        
+        // If this movement is an Update (instead of Insert),
+        // we need to reduce the old stock holding before updating the new one
+        if (sm.ID != Guid.Empty)
+            StockHoldingStore.UpdateStockHolding(sm.ID,StockHoldingStore.Action.Decrease);
+    }
+
     protected override void AfterSave(StockMovement sm)
     {
+        // Update the Relevant StockHolding with the details of this movement
+        StockHoldingStore.UpdateStockHolding(sm.ID,StockHoldingStore.Action.Increase);
+        
+        // Update the Job requisition item status (if applicable)
         if (sm.JobRequisitionItem.ID != Guid.Empty)
             JobRequisitionItemStore.UpdateStatus(
                 this, 
@@ -26,7 +40,14 @@ public class StockMovementStore : BaseStore<StockMovement>
     protected override void BeforeDelete(StockMovement entity)
     {
         base.BeforeDelete(entity);
-        entity.JobRequisitionItem.ID = Provider.Query<StockMovement>(
+        
+        // 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(entity.ID,StockHoldingStore.Action.Decrease);
+        
+        // Now let's pull the requisition ID, so that we can clean this up properly
+        // in AfterDelete()
+        entity.JobRequisitionItem.ID = Provider.Query(
                 new Filter<StockMovement>(x => x.ID).IsEqualTo(entity.ID),
                 new Columns<StockMovement>(x => x.JobRequisitionItem.ID)
             ).Rows