Преглед изворни кода

Added Issues to Stock Summary; added product istance to stock summary and improved aggregates. Added minimum stock behaviour

Kenric Nugteren пре 1 година
родитељ
комит
cc39cb8f0e

+ 2 - 2
prs.classes/Entities/Job/JobBillOfMaterialsItem.cs

@@ -53,10 +53,10 @@ namespace Comal.Classes
         public PurchaseOrderItemLink PurchaseOrderItem { get; set; }
 
         public ManufacturingPacketLink Packet { get; set; }
-        
+
         static JobBillOfMaterialsItem()
         {
-            LinkedProperties.Register<JobBillOfMaterialsItem, ProductStyleLink, Guid>(x=>x.Product.DefaultInstance.Style, x => x.ID, x => x.Style.ID);
+            LinkedProperties.Register<JobBillOfMaterialsItem, ProductStyleLink, Guid>(x => x.Product.DefaultInstance.Style, x => x.ID, x => x.Style.ID);
             
             StockEntity.LinkStockDimensions<JobBillOfMaterialsItem>();
         }

+ 3 - 0
prs.classes/Entities/Product/ProductLink.cs

@@ -113,6 +113,9 @@ namespace Comal.Classes
         [Obsolete]
         public int MinimumStockLevel { get; set; }
 
+        [NullEditor]
+        public string Issues { get; set; }
+
         public override string ToString()
         {
             return string.Format("{0}: {1}", Code, Name);

+ 49 - 15
prs.classes/Entities/Stock/StockSummary.cs

@@ -25,12 +25,28 @@ namespace Comal.Classes
         public override Filter<JobBillOfMaterialsItem> Filter =>
             new Filter<JobBillOfMaterialsItem>(x => x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue);
     }
-    
+    public class StockSummaryMinimumStockAggregate : CoreAggregate<StockSummary, ProductInstance, int>
+    {
+        public override Expression<Func<ProductInstance, int>> Aggregate => x => x.MinimumStockLevel;
+
+        public override AggregateCalculation Calculation => AggregateCalculation.Sum;
+
+        public override Dictionary<Expression<Func<ProductInstance, object>>, Expression<Func<StockSummary, object>>> Links =>
+            new Dictionary<Expression<Func<ProductInstance, object>>, Expression<Func<StockSummary, object>>>()
+            {
+                { JobBillOfMaterialsItem => JobBillOfMaterialsItem.Product.ID, summary => summary.Product.ID },
+                { JobBillOfMaterialsItem => JobBillOfMaterialsItem.Style.ID, summary => summary.Style.ID },
+                { JobBillOfMaterialsItem => JobBillOfMaterialsItem.Dimensions.UnitSize, summary => summary.Dimensions.UnitSize }
+            };
+
+    }
+
     public class StockSummaryIssuedAggregate : CoreAggregate<StockSummary, StockMovement, double>
     {
         public override Expression<Func<StockMovement, double>> Aggregate => x => x.Issued;
 
-        public override Filter<StockMovement> Filter => new Filter<StockMovement>(x => x.IsTransfer).IsEqualTo(false);
+        public override Filter<StockMovement> Filter => new Filter<StockMovement>(x => x.IsTransfer).IsEqualTo(false)
+            .And(x => x.Job.ID).IsNotEqualTo(Guid.Empty);
 
         public override AggregateCalculation Calculation => AggregateCalculation.Sum;
 
@@ -124,7 +140,7 @@ namespace Comal.Classes
 
         public Expression<Func<StockSummary, double>>[] Modifiers => new Expression<Func<StockSummary, double>>[]
         {
-            x => x.Product.DefaultInstance.MinimumStockLevel,
+            x => x.MinimumStockLevel,
             x => x.NettRequired
         };
 
@@ -137,12 +153,16 @@ namespace Comal.Classes
     {
         protected override void Configure()
         {
+            // 
+            AddTable<ProductInstance>()
+                .AddConstant<object?>(x => x.Job.ID, null);
+
             // BOM
-            AddTable<JobBillOfMaterialsItem>();
+            AddTable<JobBillOfMaterialsItem>(new Filter<JobBillOfMaterialsItem>(x => x.Job.ID).IsNotEqualTo(Guid.Empty));
             // Stock Holdings
-            AddTable<StockMovement>();
+            AddTable<StockMovement>(new Filter<StockMovement>(x => x.Job.ID).IsNotEqualTo(Guid.Empty));
             // On Order
-            AddTable<PurchaseOrderItem>();
+            AddTable<PurchaseOrderItem>(new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue));
         }
 
         public override bool Distinct => true;
@@ -160,7 +180,16 @@ namespace Comal.Classes
             new Column<IJobMaterial>(x => x.Dimensions.Weight)
         };
     }
-    
+
+    public class StockSummaryCalculatedField : IFormula<StockSummary, double>
+    {
+        public Expression<Func<StockSummary, double>> Value => x => 0.0;
+        public Expression<Func<StockSummary, double>>[] Modifiers => new Expression<Func<StockSummary, double>>[] { };
+        public FormulaOperator Operator => FormulaOperator.Constant;
+        public FormulaType Type => FormulaType.Virtual;
+    }
+
+
     [UserTracking(typeof(Product))]
     [AutoEntity(typeof(StockSummaryUnionGenerator))]
     public class StockSummary : StockEntity, IJobMaterial, IRemotable, IPersistent, ILicense<WarehouseLicense>
@@ -184,38 +213,43 @@ namespace Comal.Classes
         [DoubleEditor]
         [Aggregate(typeof(StockSummaryBOMAggregate))]
         public double BillOfMaterials { get; set; }
-        
+
         [EditorSequence(5)]
+        [IntegerEditor]
+        [Aggregate(typeof(StockSummaryMinimumStockAggregate))]
+        public int MinimumStockLevel { get; set; }
+
+        [EditorSequence(6)]
         [Aggregate(typeof(StockSummaryIssuedAggregate))]
         [DoubleEditor]
         public double Issued { get; set; }
         
-        [EditorSequence(6)]
+        [EditorSequence(7)]
         [Formula(typeof(StockSummaryTotalRequiredFormula))]
         public double TotalRequired { get; set; }
         
-        [EditorSequence(7)]
+        [EditorSequence(8)]
         [Formula(typeof(StockSummaryNettRequiredFormula))]
         public double NettRequired { get; set; }
 
-        [EditorSequence(8)]
+        [EditorSequence(9)]
         [Aggregate(typeof(StockSummaryAllStockAggregate))]
         [DoubleEditor]
         public double AllStock { get; set; }
 
-        [EditorSequence(9)]
+        [EditorSequence(10)]
         [DoubleEditor]
         [Aggregate(typeof(StockSummaryOrderAggregate))]
         public double OnOrder { get; set; }
         
-        [EditorSequence(10)]
+        [EditorSequence(11)]
         [Formula(typeof(StockSummaryTotalStockFormula))]
         [DoubleEditor]
         public double TotalStock { get; set; }
 
-        [EditorSequence(11)]
+        [EditorSequence(12)]
         [DoubleEditor]
-        [Formula(typeof(StockSummaryBalanceAvailableFormula))]
+        [Formula(typeof(StockSummaryCalculatedField))]
         public double BalanceAvailable { get; set; }
     }
 }

+ 4 - 0
prs.classes/PRSClasses.csproj

@@ -24,6 +24,10 @@
       <EmbeddedResource Remove="PRSClasses\**" />
     </ItemGroup>
 
+    <ItemGroup>
+      <Compile Remove="Entities\Product\ProductForecast.cs" />
+    </ItemGroup>
+
     <ItemGroup>
       <PackageReference Include="Fody" Version="6.6.3">
         <PrivateAssets>all</PrivateAssets>

+ 1 - 1
prs.desktop/Panels/StockSummary/StockSummaryControl.xaml

@@ -8,7 +8,7 @@
              mc:Ignorable="d"
              d:DesignHeight="900" d:DesignWidth="1000">
     <dynamicGrid:DynamicSplitPanel x:Name="SplitPanel" MasterCaption="Stock Summary" DetailCaption="Product Groups" View="Combined" Anchor="Detail" AnchorWidth="350" AllowableViews="Master,Combined" OnChanged="SplitPanel_OnOnChanged" DetailHeight="500">
-        
+
         <dynamicGrid:DynamicSplitPanel.Header>
             <Border BorderBrush="DimGray" BorderThickness="0.75">
                 <Label Content="Stock Summary" HorizontalContentAlignment="Center"/>

+ 109 - 112
prs.desktop/Panels/StockSummary/StockSummaryControl.xaml.cs

@@ -6,138 +6,135 @@ using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public partial class StockSummaryControl : UserControl
 {
-    public partial class StockSummaryControl : UserControl
+
+    private enum Suppress
     {
+        This
+    }
 
-        private enum Suppress
-        {
-            This
-        }
+    public StockSummaryControl()
+    {
+        using (new EventSuppressor(Suppress.This))
+            InitializeComponent();
+    }
     
-        public StockSummaryControl()
-        {
-            using (new EventSuppressor(Suppress.This))
-                InitializeComponent();
-        }
-        
-        public void Setup()
+    public void Setup()
+    {
+        using (new EventSuppressor(Suppress.This))
         {
-            using (new EventSuppressor(Suppress.This))
-            {
-                DoLoadSettings();
-
-
-                SplitPanel.View = Properties.SplitPanelSettings.View;
-                SplitPanel.AnchorWidth = Properties.SplitPanelSettings.AnchorWidth;
-                SplitPanel.DetailHeight = Properties.SplitPanelSettings.DetailHeight;
-                
-                GroupSelector.Settings = Properties.GroupSettings;
-                GroupSelector.Setup();
-                GroupSelector.Selection = Properties.GroupSelection;
-                SummaryGrid.GroupIDs = Properties.GroupSelection.Groups;
-                
-                JobSelector.Settings = Properties.JobSettings;
-                JobSelector.Setup();
-                JobSelector.Selection = Properties.JobSelection;
-                SummaryGrid.JobIDs = Properties.JobSelection.Jobs;
-
-                SummaryGrid.Refresh(true, false);
-            }
-        }
+            DoLoadSettings();
 
-        public void Shutdown(CancelEventArgs? cancel)
-        {
+            SplitPanel.View = Properties.SplitPanelSettings.View;
+            SplitPanel.AnchorWidth = Properties.SplitPanelSettings.AnchorWidth;
+            SplitPanel.DetailHeight = Properties.SplitPanelSettings.DetailHeight;
             
-        }
+            GroupSelector.Settings = Properties.GroupSettings;
+            GroupSelector.Setup();
+            GroupSelector.Selection = Properties.GroupSelection;
+            SummaryGrid.GroupIDs = Properties.GroupSelection.Groups;
+            
+            JobSelector.Settings = Properties.JobSettings;
+            JobSelector.Setup();
+            JobSelector.Selection = Properties.JobSelection;
+            SummaryGrid.JobIDs = Properties.JobSelection.Jobs;
 
-        public void Refresh()
-        {
-            SummaryGrid.Refresh(false,true);
+            SummaryGrid.Refresh(true, false);
         }
+    }
 
-        public StockSummaryProperties Properties { get; set; }
+    public void Shutdown(CancelEventArgs? cancel)
+    {
         
-        private void DoLoadSettings()
-        {
-            Properties = LoadSettings?.Invoke(this) ?? new StockSummaryProperties();
+    }
 
-        }
-        
-        private void DoSaveSettings()
-        {
-            SaveSettings?.Invoke(this, Properties);
-        }
-        
-        public event LoadSettings<StockSummaryProperties>? LoadSettings;
-        
-        public event SaveSettings<StockSummaryProperties>? SaveSettings;
+    public void Refresh()
+    {
+        SummaryGrid.Refresh(false,true);
+    }
 
-        private void GroupSelector_OnSettingsChanged(object sender, ProductGroupSelectorSettingsChangedArgs args)
-        {
-            if (EventSuppressor.IsSet(Suppress.This))
-                return;
-                
-            Properties.GroupSettings = args.Settings;
-            DoSaveSettings();
-        }
+    public StockSummaryProperties Properties { get; set; }
+    
+    private void DoLoadSettings()
+    {
+        Properties = LoadSettings?.Invoke(this) ?? new StockSummaryProperties();
+    }
+    
+    private void DoSaveSettings()
+    {
+        SaveSettings?.Invoke(this, Properties);
+    }
+    
+    public event LoadSettings<StockSummaryProperties>? LoadSettings;
+    
+    public event SaveSettings<StockSummaryProperties>? SaveSettings;
 
-        private void GroupSelector_OnSelectionChanged(object sender, ProductGroupSelectorSelectionChangedArgs args)
-        {
-            if (EventSuppressor.IsSet(Suppress.This))
-                return;
-                
-            Properties.GroupSelection = args.Selection;
-            DoSaveSettings();
-                
-            SummaryGrid.GroupIDs = args.Selection.Groups;
-            SummaryGrid.Refresh(false, true);
-        }
+    private void GroupSelector_OnSettingsChanged(object sender, ProductGroupSelectorSettingsChangedArgs args)
+    {
+        if (EventSuppressor.IsSet(Suppress.This))
+            return;
+            
+        Properties.GroupSettings = args.Settings;
+        DoSaveSettings();
+    }
 
-        private void JobSelector_OnSettingsChanged(object sender, JobSelectorSettingsChangedArgs args)
-        {
-            if (EventSuppressor.IsSet(Suppress.This))
-                return;
-                
-            Properties.JobSettings = args.Settings;
-            DoSaveSettings();
-                
-        }
+    private void GroupSelector_OnSelectionChanged(object sender, ProductGroupSelectorSelectionChangedArgs args)
+    {
+        if (EventSuppressor.IsSet(Suppress.This))
+            return;
+            
+        Properties.GroupSelection = args.Selection;
+        DoSaveSettings();
+            
+        SummaryGrid.GroupIDs = args.Selection.Groups;
+        SummaryGrid.Refresh(false, true);
+    }
 
-        private void JobSelector_OnSelectionChanged(object sender, JobSelectorSelectionChangedArgs args)
-        {
-            if (EventSuppressor.IsSet(Suppress.This))
-                return;
+    private void JobSelector_OnSettingsChanged(object sender, JobSelectorSettingsChangedArgs args)
+    {
+        if (EventSuppressor.IsSet(Suppress.This))
+            return;
             
-            Properties.JobSelection = args.Selection;
-            DoSaveSettings();
+        Properties.JobSettings = args.Settings;
+        DoSaveSettings();
             
-            SummaryGrid.JobIDs = args.Selection.Jobs;
-            SummaryGrid.Refresh(false, true);
-        }
+    }
+
+    private void JobSelector_OnSelectionChanged(object sender, JobSelectorSelectionChangedArgs args)
+    {
+        if (EventSuppressor.IsSet(Suppress.This))
+            return;
         
-        private void SplitPanel_OnOnChanged(object sender, DynamicSplitPanelSettings e)
-        {
-            if (EventSuppressor.IsSet(Suppress.This))
-                return;
-            
-            Properties.SplitPanelSettings = e;
-            DoSaveSettings();
-        }
+        Properties.JobSelection = args.Selection;
+        DoSaveSettings();
         
-        private void SummaryGrid_OnBeforeRefresh(object sender, BeforeRefreshEventArgs args)
-        {
-            //Progress.Show("Loading");
-            GroupSelector.IsEnabled = false;
-            JobSelector.IsEnabled = false;
-        }
+        SummaryGrid.JobIDs = args.Selection.Jobs;
+        SummaryGrid.Refresh(false, true);
+    }
+    
+    private void SplitPanel_OnOnChanged(object sender, DynamicSplitPanelSettings e)
+    {
+        if (EventSuppressor.IsSet(Suppress.This))
+            return;
+        
+        Properties.SplitPanelSettings = e;
+        DoSaveSettings();
+    }
+    
+    private void SummaryGrid_OnBeforeRefresh(object sender, BeforeRefreshEventArgs args)
+    {
+        //Progress.Show("Loading");
+        GroupSelector.IsEnabled = false;
+        JobSelector.IsEnabled = false;
+    }
 
-        private void SummaryGrid_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
-        {
-            //Progress.Close();
-            GroupSelector.IsEnabled = true;
-            JobSelector.IsEnabled = true;
-        }
+    private void SummaryGrid_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
+    {
+        //Progress.Close();
+        GroupSelector.IsEnabled = true;
+        JobSelector.IsEnabled = true;
     }
 }

+ 450 - 394
prs.desktop/Panels/StockSummary/StockSummaryGrid.cs

@@ -6,454 +6,510 @@ using System.Reflection;
 using System.Windows.Media;
 using Comal.Classes;
 using InABox.Clients;
+using InABox.Configuration;
 using InABox.Core;
 using InABox.DynamicGrid;
+using InABox.Wpf;
 using PRSDesktop.Utils;
 using Syncfusion.Data.Extensions;
 using Xceed.Wpf.Toolkit;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+
+public enum StockSummaryMinimumStockBehaviour
 {
-    public class StockSummaryGrid : DynamicDataGrid<StockSummary>, IDataModelSource
+    Cumulative,
+    Minimum
+}
+
+public class StockSummaryGrid : DynamicDataGrid<StockSummary>, IDataModelSource
+{
+    
+    public Guid[] GroupIDs { get; set; }
+    public Guid[] JobIDs { get; set; }
+
+    private StockSummaryMinimumStockBehaviour MinStockBehaviour { get; set; }
+    
+    public StockSummaryGrid() : base()
     {
+
+        ColumnsTag = "StockSummaryGrid";
+
+        GroupIDs = new Guid[] { };
+        JobIDs = new Guid[] { };
         
-        public Guid[] GroupIDs { get; set; }
-        public Guid[] JobIDs { get; set; }
-        
-        public StockSummaryGrid() : base()
+        HiddenColumns.Add(x => x.Product.ID);
+        HiddenColumns.Add(x => x.Product.Issues);
+        HiddenColumns.Add(x => x.Style.ID);
+        HiddenColumns.Add(x => x.Job.ID);
+        HiddenColumns.Add(x => x.Dimensions.UnitSize);
+        HiddenColumns.Add(x => x.Product.DefaultInstance.MinimumStockLevel);
+        HiddenColumns.Add(x => x.Issued);
+        HiddenColumns.Add(x => x.TotalRequired);
+        HiddenColumns.Add(x => x.NettRequired);
+        HiddenColumns.Add(x => x.AllStock);
+        HiddenColumns.Add(x => x.OnOrder);
+        HiddenColumns.Add(x => x.TotalStock);
+        HiddenColumns.Add(x => x.BalanceAvailable);
+
+        HiddenColumns.Add(x => x.Product.Image.ID);
+        HiddenColumns.Add(x => x.Product.Image.FileName);
+        ActionColumns.Add(new DynamicIssuesColumn<Product>(this, CoreUtils.GetFullPropertyName<StockSummary, string>(x => x.Product.Issues, "."), LoadProducts));
+        ActionColumns.Add(new DynamicImagePreviewColumn<StockSummary>(x => x.Product.Image)
+            { Position = DynamicActionColumnPosition.Start });
+
+        OnCellDoubleClick += StockSummaryGrid_OnCellDoubleClick;
+
+        MinStockBehaviour = new GlobalConfiguration<StockSummaryPanelSettings>().Load().MinimumStockBehaviour;
+    }
+
+    private Product[] LoadProducts(CoreRow[] arg)
+    {
+        return Client.Query(
+            new Filter<Product>(x => x.ID).InList(arg.Select(x => x.Get<StockSummary, Guid>(x => x.Product.ID)).ToArray()),
+            new Columns<Product>(x => x.ID, x => x.Issues))
+            .ToObjects<Product>().ToArray();
+    }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+
+        options.AddRange(
+            DynamicGridOption.RecordCount,
+            DynamicGridOption.SelectColumns,
+            DynamicGridOption.FilterRows,
+            DynamicGridOption.ExportData,
+            DynamicGridOption.MultiSelect
+        );
+    }
+
+    public override DynamicGridColumns GenerateColumns()
+    {
+        var columns = new DynamicGridColumns();
+        columns.Add<StockSummary, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, string>(x => x.Style.Code, 120, "Style Code", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, string>(x => x.Dimensions.UnitSize, 120, "Unit Size", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, int>(x => x.MinimumStockLevel, 120, "Minimum Stock Level", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, double>(x => x.BillOfMaterials, 120, "BOM", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, double>(x => x.Issued, 120, "Issued", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, double>(x => x.AllStock, 120, "Stock", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, double>(x => x.OnOrder, 120, "On Order", "", Alignment.MiddleCenter);
+        columns.Add<StockSummary, double>(x => x.BalanceAvailable, 120, "Balance Available", "", Alignment.MiddleCenter);
+        return columns;
+    }
+
+    private void ShowDetailGrid<TEntity>(
+        String columnname, 
+        Expression<Func<TEntity,object?>> productcol, 
+        Guid productid, 
+        Expression<Func<TEntity,object?>> stylecol, 
+        Guid? styleid, 
+        Expression<Func<TEntity,object?>> unitcol, 
+        String unitsize,
+        Expression<Func<TEntity,object?>>? jobcol,
+        Filter<TEntity>? extrafilter,
+        Func<CoreRow,bool>? rowfilter
+    )
+    {
+        var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
+        if (grid == null)
         {
+            MessageBox.Show($"Cannot create Grid for [{typeof(TEntity).Name}]");
+            return;
+        }
+        grid.ColumnsTag = $"{ColumnsTag}.{columnname}";
+        grid.Reconfigure(options => { options.BeginUpdate().Clear().AddRange(DynamicGridOption.FilterRows, DynamicGridOption.SelectColumns).EndUpdate(); });
+        grid.OnDefineFilter += t =>
+        {
+            var filter = new Filter<TEntity>(productcol).IsEqualTo(productid)
+                .And(unitcol).IsEqualTo(unitsize);
+            if (styleid.HasValue)
+                filter = filter.And(stylecol).IsEqualTo(styleid);
+                
+            if (jobcol != null)
+                filter = filter.And(new Filter<TEntity>(jobcol).InList(JobIDs).Or(jobcol).IsEqualTo(Guid.Empty));
+                
+            if (extrafilter != null)
+                filter = filter.And(extrafilter);
+            return filter;
+        };
+        grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
+        var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(columnname)} Calculation", (grid as BaseDynamicGrid)!);
+        window.ShowDialog();
+    }
+    
+    private void StockSummaryGrid_OnCellDoubleClick(object sender, DynamicGridCellClickEventArgs args)
+    {
 
-            ColumnsTag = "StockSummaryGrid";
+       
+        
+        Guid productid = args.Row.Get<StockSummary, Guid>(c => c.Product.ID);
+        Guid? styleid = HasStyle() ? args.Row.Get<StockSummary, Guid>(c => c.Style.ID) : null;
+        String unitsize = args.Row.Get<StockSummary, String>(c => c.Dimensions.UnitSize);
 
-            GroupIDs = new Guid[] { };
-            JobIDs = new Guid[] { };
-            
-            HiddenColumns.Add(x => x.Product.ID);
-            HiddenColumns.Add(x => x.Style.ID);
-            HiddenColumns.Add(x => x.Job.ID);
-            HiddenColumns.Add(x => x.Dimensions.UnitSize);
-            HiddenColumns.Add(x => x.Product.DefaultInstance.MinimumStockLevel);
-            HiddenColumns.Add(x => x.Issued);
-            HiddenColumns.Add(x => x.TotalRequired);
-            HiddenColumns.Add(x => x.NettRequired);
-            HiddenColumns.Add(x => x.AllStock);
-            HiddenColumns.Add(x => x.OnOrder);
-            HiddenColumns.Add(x => x.TotalStock);
-            HiddenColumns.Add(x => x.BalanceAvailable);
-
-            HiddenColumns.Add(x => x.Product.Image.ID);
-            HiddenColumns.Add(x => x.Product.Image.FileName);
-            ActionColumns.Add(new DynamicImagePreviewColumn<StockSummary>(x => x.Product.Image)
-                { Position = DynamicActionColumnPosition.Start });
-            
-            OnCellDoubleClick += StockSummaryGrid_OnCellDoubleClick;
+        if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".")))
+        {
+            ShowDetailGrid<JobBillOfMaterialsItem>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                x => x.Job.ID,
+                new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
+                null
+            );
         }
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, int>(x => x.MinimumStockLevel, ".")))
         {
-            base.DoReconfigure(options);
-
-            options.AddRange(
-                DynamicGridOption.RecordCount,
-                DynamicGridOption.SelectColumns,
-                DynamicGridOption.FilterRows,
-                DynamicGridOption.ExportData,
-                DynamicGridOption.MultiSelect
+            ShowDetailGrid<ProductInstance>(
+                args.Column.ColumnName,
+                x => x.Product.ID,
+                productid,
+                x => x.Style.ID,
+                styleid,
+                x => x.Dimensions.UnitSize,
+                unitsize,
+                null,
+                null,
+                null
             );
         }
-
-        public override DynamicGridColumns GenerateColumns()
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".")))
         {
-            var columns = new DynamicGridColumns();
-            columns.Add<StockSummary, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, string>(x => x.Style.Code, 120, "Style Code", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, string>(x => x.Dimensions.UnitSize, 120, "Unit Size", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, int>(x => x.Product.DefaultInstance.MinimumStockLevel, 120, "Minimum Stock Level", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, double>(x => x.BillOfMaterials, 120, "BOM", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, double>(x => x.Issued, 120, "Issued", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, double>(x => x.AllStock, 120, "Stock", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, double>(x => x.OnOrder, 120, "On Order", "", Alignment.MiddleCenter);
-            columns.Add<StockSummary, double>(x => x.BalanceAvailable, 120, "Balance Available", "", Alignment.MiddleCenter);
-            return columns;
+            ShowDetailGrid<StockMovement>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                x => x.Job.ID,
+                new Filter<StockMovement>(x=>x.IsTransfer).IsEqualTo(false).And(x=>x.Issued).IsNotEqualTo(0.0F),
+                null
+            );
         }
-
-        private void ShowDetailGrid<TEntity>(
-            String columnname, 
-            Expression<Func<TEntity,object?>> productcol, 
-            Guid productid, 
-            Expression<Func<TEntity,object?>> stylecol, 
-            Guid? styleid, 
-            Expression<Func<TEntity,object?>> unitcol, 
-            String unitsize,
-            Expression<Func<TEntity,object?>>? jobcol,
-            Filter<TEntity>? extrafilter,
-            Func<CoreRow,bool>? rowfilter
-        )
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalRequired, ".")))
         {
-            var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
-            if (grid == null)
-            {
-                MessageBox.Show($"Cannot create Grid for [{typeof(TEntity).Name}]");
-                return;
-            }
-            grid.ColumnsTag = $"{ColumnsTag}.{columnname}";
-            grid.Reconfigure(options => { options.BeginUpdate().Clear().AddRange(DynamicGridOption.FilterRows, DynamicGridOption.SelectColumns).EndUpdate(); });
-            grid.OnDefineFilter += t =>
-            {
-                var filter = new Filter<TEntity>(productcol).IsEqualTo(productid)
-                    .And(unitcol).IsEqualTo(unitsize);
-                if (styleid.HasValue)
-                    filter = filter.And(stylecol).IsEqualTo(styleid);
-                    
-                if (jobcol != null)
-                    filter = filter.And(jobcol).InList(JobIDs);
-                    
-                if (extrafilter != null)
-                    filter = filter.And(extrafilter);
-                return filter;
-            };
-            grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
-            var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(columnname)} Calculation", (grid as BaseDynamicGrid)!);
-            window.ShowDialog();
+            ShowDetailGrid<StockSummary>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                x => x.Job.ID,
+                null,
+                null
+            );
         }
-        
-        private void StockSummaryGrid_OnCellDoubleClick(object sender, DynamicGridCellClickEventArgs args)
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".")))
         {
-
-           
-            
-            Guid productid = args.Row.Get<StockSummary, Guid>(c => c.Product.ID);
-            Guid? styleid = HasStyle() ? args.Row.Get<StockSummary, Guid>(c => c.Style.ID) : null;
-            String unitsize = args.Row.Get<StockSummary, String>(c => c.Dimensions.UnitSize);
-
-            if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".")))
-            {
-                ShowDetailGrid<JobBillOfMaterialsItem>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    x => x.Job.ID,
-                    new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
-                    null
-                );
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".")))
-            {
-                ShowDetailGrid<StockMovement>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    x => x.Job.ID,
-                    new Filter<StockMovement>(x=>x.IsTransfer).IsEqualTo(false).And(x=>x.Issued).IsNotEqualTo(0.0F),
-                    null
-                );
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalRequired, ".")))
-            {
-                ShowDetailGrid<StockSummary>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    x => x.Job.ID,
-                    null,
-                    null
-                );
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".")))
-            {
-                ShowDetailGrid<StockSummary>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    x => x.Job.ID,
-                    null,
-                    null
-                );                
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".")))
-            {
-                ShowDetailGrid<StockHolding>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    null,
-                    null,
-                    (row) => row.Get<StockHolding,double>(x=>x.Units) != 0.0F
-                );               
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".")))
-            {
-                ShowDetailGrid<PurchaseOrderItem>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    null,
-                    null,
-                    null
-                );                  
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalStock, ".")))
-            {
-                ShowDetailGrid<StockSummary>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    x => x.Job.ID,
-                    null,
-                    null
-                );                 
-            }
-            else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".")))
-            {
-                ShowDetailGrid<StockSummary>(
-                    args.Column.ColumnName, 
-                    x => x.Product.ID, 
-                    productid, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x=>x.Dimensions.UnitSize,
-                    unitsize,
-                    x => x.Job.ID,
-                    null,
-                    null
-                );                
-            }
-            
+            ShowDetailGrid<StockSummary>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                x => x.Job.ID,
+                null,
+                null
+            );                
         }
-
-        private bool HasStyle()
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".")))
         {
-            return DataColumns().ColumnNames().Any(x => x.StartsWith("Style.") && !x.Equals("Style.ID"));
+            ShowDetailGrid<StockHolding>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                null,
+                null,
+                (row) => row.Get<StockHolding,double>(x=>x.Units) != 0.0F
+            );               
         }
-
-        private Filters<StockSummary> GetFilters() 
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".")))
         {
-            var filters = new Filters<StockSummary>();
-            filters.Add(FilterComponent.GetFilter());
-            
-            Filter<StockSummary>? _groupfilter = !GroupIDs.Any()
-                ? new Filter<StockSummary>().None()
-                : !GroupIDs.Contains(CoreUtils.FullGuid)
-                    ? new Filter<StockSummary>(x => x.Product.Group.ID).InList(GroupIDs)
-                    : null;
-            filters.Add(_groupfilter);
-
-            var jobFilter = !JobIDs.Any()
-                ? new Filter<StockSummary>().None()
-                : new Filter<StockSummary>(x => x.Job.ID).InList(JobIDs);
-            filters.Add(jobFilter);
-
-            //Filter<StockSummary> _productfilter = new Filter<StockSummary>(x => x.Product.ID).IsNotEqualTo(Guid.Empty);
-            //filters.Add(_productfilter);
-
-            return filters;
+            ShowDetailGrid<PurchaseOrderItem>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                null,
+                null,
+                null
+            );                  
         }
-
-        private Tuple<Guid,Guid?,String>[] GetKeys(IEnumerable<CoreRow> rows, Columns<StockSummary> columns, bool hasstyle)
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalStock, ".")))
         {
-            int productcol = columns.IndexOf(x => x.Product.ID);
-            int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
-            int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
-            
-            var result = rows.Select(r => new Tuple<Guid, Guid?, String>(
-                (Guid)(r.Values[productcol] ?? Guid.Empty),
-                (stylecol != -1) ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null,
-                (String)(r.Values[unitcol] ?? ""))
-            ).Distinct().ToArray();
-            
-            return result;
+            ShowDetailGrid<StockSummary>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                x => x.Job.ID,
+                null,
+                null
+            );                 
         }
-
-        private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid productid, Guid? styleid, String unitsize) where TSource : IJobMaterial
+        else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".")))
         {
-            int productcol = columns.IndexOf(x => x.Product.ID);
-            int stylecol = styleid.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
-            int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize); 
-            
-            var subset = rows
-                .Where(r=>
-                    Guid.Equals(r.Values[productcol], productid)
-                    && (!styleid.HasValue || Guid.Equals(r.Values[stylecol], styleid))
-                    && String.Equals(r.Values[unitcol], unitsize)
-                );
-            
-            return subset.ToArray();
+            ShowDetailGrid<StockSummary>(
+                args.Column.ColumnName, 
+                x => x.Product.ID, 
+                productid, 
+                x => x.Style.ID, 
+                styleid, 
+                x=>x.Dimensions.UnitSize,
+                unitsize,
+                x => x.Job.ID,
+                null,
+                null
+            );                
         }
+        
+    }
+
+    private bool HasStyle()
+    {
+        return DataColumns().ColumnNames().Any(x => x.StartsWith("Style.") && !x.Equals("Style.ID"));
+    }
+
+    private Filters<StockSummary> GetFilters() 
+    {
+        var filters = new Filters<StockSummary>();
+        filters.Add(FilterComponent.GetFilter());
+        
+        Filter<StockSummary>? _groupfilter = !GroupIDs.Any()
+            ? new Filter<StockSummary>().None()
+            : !GroupIDs.Contains(CoreUtils.FullGuid)
+                ? new Filter<StockSummary>(x => x.Product.Group.ID).InList(GroupIDs)
+                : null;
+        filters.Add(_groupfilter);
+
+        var jobFilter = !JobIDs.Any()
+            ? new Filter<StockSummary>().None()
+            : new Filter<StockSummary>(x => x.Job.ID).InList(JobIDs);
+        filters.Add(jobFilter.Or(x => x.Job.ID).IsEqualTo(Guid.Empty));
+
+        //Filter<StockSummary> _productfilter = new Filter<StockSummary>(x => x.Product.ID).IsNotEqualTo(Guid.Empty);
+        //filters.Add(_productfilter);
+
+        return filters;
+    }
+
+    private Tuple<Guid,Guid?,String>[] GetKeys(IEnumerable<CoreRow> rows, Columns<StockSummary> columns, bool hasstyle)
+    {
+        int productcol = columns.IndexOf(x => x.Product.ID);
+        int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
+        int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
+        
+        var result = rows.Select(r => new Tuple<Guid, Guid?, String>(
+            (Guid)(r.Values[productcol] ?? Guid.Empty),
+            (stylecol != -1) ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null,
+            (String)(r.Values[unitcol] ?? ""))
+        ).Distinct().ToArray();
+        
+        return result;
+    }
 
-        private double Aggregate<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, bool hasstyle, bool hasjob, Expression<Func<TSource, object>> source, CoreRow target, Expression<Func<StockSummary, object>> aggregate )
+    private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid productid, Guid? styleid, String unitsize) where TSource : IJobMaterial
+    {
+        int productcol = columns.IndexOf(x => x.Product.ID);
+        int stylecol = styleid.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
+        int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize); 
+        
+        var subset = rows
+            .Where(r=>
+                Guid.Equals(r.Values[productcol], productid)
+                && (!styleid.HasValue || Guid.Equals(r.Values[stylecol], styleid))
+                && String.Equals(r.Values[unitcol], unitsize)
+            );
+        
+        return subset.ToArray();
+    }
+
+    private double Aggregate<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, bool hasstyle, bool hasjob, Expression<Func<TSource, object>> source, CoreRow target, Expression<Func<StockSummary, object>> aggregate )
+    {
+        int srcol = columns.IndexOf(source);
+        
+        if (srcol == -1)
+            return 0.00;
+
+        var total =  rows.Aggregate(0d, (value, row) => value + (double)(row.Values[srcol] ?? 0.0d));
+        
+        // int productcol = columns.IndexOf(x => x.Product.ID);
+        // int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
+        // int jobcol = hasjob ? columns.IndexOf(x => x.Job.ID) : -1;
+        // int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
+        //
+        // var tuples = rows.Select(r => new Tuple<Guid, Guid?, Guid?, String, double>(
+        //     (Guid)(r.Values[productcol] ?? Guid.Empty),
+        //     (hasstyle ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null),
+        //     (hasjob ? (Guid)(r.Values[jobcol] ?? Guid.Empty) : null),
+        //     (String)(r.Values[unitcol] ?? ""),
+        //     (double)(r.Values[aggcol] ?? 0.0d))
+        // ).ToArray();
+        //
+        // var total = tuples.Aggregate(0d, (value, tuple) => value + tuple.Item5);
+
+        target.Set(aggregate, total);
+        return total;
+    }
+    
+    protected override void Reload(Filters<StockSummary> criteria, Columns<StockSummary> columns, ref SortOrder<StockSummary>? sort,
+        Action<CoreTable?, Exception?> action)
+    {
+        var filter = GetFilters().Combine();
+        
+        new Client<StockSummary>().Query(filter,columns,sort, (o,e) =>
         {
-            int srcol = columns.IndexOf(source);
+            if(o is null)
+            {
+                Dispatcher.BeginInvoke(() =>
+                {
+                    MessageWindow.ShowError("Failed to load data", e);
+                });
+                return;
+            }
+            var pids = o.ExtractValues<StockSummary, Guid>(x => x.Product.ID).ToArray();
             
-            if (srcol == -1)
-                return 0.00;
+            MultiQuery query = new MultiQuery();
+            query.Add<StockHolding>(
+                new Filter<StockHolding>(x => x.Product.ID).InList(pids)
+                    .And(x => x.Units).IsNotEqualTo(0.0),
+                new Columns<StockHolding>(x => x.Product.ID)
+                    .Add(x => x.Style.ID)
+                    .Add(x => x.Dimensions.UnitSize)
+                    .Add(x => x.Units)
+            );
+            query.Add<PurchaseOrderItem>(
+                new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue)
+                    .And(x => x.Product.ID).InList(pids),
+                new Columns<PurchaseOrderItem>(x => x.Product.ID)
+                    .Add(x => x.Style.ID)
+                    .Add(x => x.Dimensions.UnitSize)
+                    .Add(x => x.Qty)
+            );
+            query.Query();
+            var holdings = query.Get<StockHolding>();
+            Columns<StockHolding> holdingcolumns = new Columns<StockHolding>(holdings.Columns.Select(x => x.ColumnName));
+            
+            var orders = query.Get<PurchaseOrderItem>();
+            Columns<PurchaseOrderItem> ordercolumns = new Columns<PurchaseOrderItem>(orders.Columns.Select(x => x.ColumnName));
 
-            var total =  rows.Aggregate(0d, (value, row) => value + (double)(row.Values[srcol] ?? 0.0d));
             
-            // int productcol = columns.IndexOf(x => x.Product.ID);
-            // int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
-            // int jobcol = hasjob ? columns.IndexOf(x => x.Job.ID) : -1;
-            // int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
-            //
-            // var tuples = rows.Select(r => new Tuple<Guid, Guid?, Guid?, String, double>(
-            //     (Guid)(r.Values[productcol] ?? Guid.Empty),
-            //     (hasstyle ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null),
-            //     (hasjob ? (Guid)(r.Values[jobcol] ?? Guid.Empty) : null),
-            //     (String)(r.Values[unitcol] ?? ""),
-            //     (double)(r.Values[aggcol] ?? 0.0d))
-            // ).ToArray();
-            //
-            // var total = tuples.Aggregate(0d, (value, tuple) => value + tuple.Item5);
-
-            target.Set(aggregate, total);
-            return total;
-        }
-        
-        protected override void Reload(Filters<StockSummary> criteria, Columns<StockSummary> columns, ref SortOrder<StockSummary>? sort,
-            Action<CoreTable?, Exception?> action)
-        {
-            var filter = GetFilters().Combine();
+            CoreTable table = new CoreTable();
+            table.LoadColumns(columns);
             
-            new Client<StockSummary>().Query(filter,columns,sort, (o,e) =>
+            if (o != null)
             {
                 
-                var pids = o.ExtractValues<StockSummary, Guid>(x => x.Product.ID).ToArray();
-                
-                MultiQuery query = new MultiQuery();
-                query.Add<StockHolding>(
-                    new Filter<StockHolding>(x => x.Product.ID).InList(pids),
-                    new Columns<StockHolding>(x => x.Product.ID)
-                        .Add(x => x.Style.ID)
-                        .Add(x => x.Dimensions.UnitSize)
-                        .Add(x => x.Units)
-                );
-                query.Add<PurchaseOrderItem>(
-                    new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue)
-                        .And(x => x.Product.ID).InList(pids),
-                    new Columns<PurchaseOrderItem>(x => x.Product.ID)
-                        .Add(x => x.Style.ID)
-                        .Add(x => x.Dimensions.UnitSize)
-                        .Add(x => x.Qty)
-                );
-                query.Query();
-                var holdings = query.Get<StockHolding>();
-                Columns<StockHolding> holdingcolumns = new Columns<StockHolding>(holdings.Columns.Select(x => x.ColumnName));
-                
-                var orders = query.Get<PurchaseOrderItem>();
-                Columns<PurchaseOrderItem> ordercolumns = new Columns<PurchaseOrderItem>(orders.Columns.Select(x => x.ColumnName));
+                bool bHasStyle = HasStyle();
 
+                var keys = GetKeys(o.Rows, columns, bHasStyle);
                 
-                CoreTable table = new CoreTable();
-                table.LoadColumns(columns);
-                
-                if (o != null)
+                foreach (var key in keys)
                 {
                     
-                    bool bHasStyle = HasStyle();
-
-                    var keys = GetKeys(o.Rows, columns, bHasStyle);
-                    
-                    foreach (var key in keys)
+                    var rows = GetRows(o.Rows, columns, key.Item1, key.Item2, key.Item3);
+                    if (rows.Any())
                     {
                         
-                        var rows = GetRows(o.Rows, columns, key.Item1, key.Item2, key.Item3);
-                        if (rows.Any())
+                        CoreRow newrow = table.NewRow();
+                        newrow.LoadValues(rows.First().Values);
+                        Aggregate(rows, columns, bHasStyle, true, x => x.BillOfMaterials, newrow, x => x.BillOfMaterials);
+                        Aggregate(rows, columns, bHasStyle, true, x => x.Issued, newrow, x => x.Issued);
+                        Aggregate(rows, columns, bHasStyle, true, x => x.TotalRequired, newrow, x => x.TotalRequired);
+                        var nettrequired = Aggregate(rows, columns, bHasStyle, true, x => x.NettRequired, newrow, x => x.NettRequired);
+                        var holdingrows = GetRows(holdings.Rows, holdingcolumns, key.Item1, key.Item2, key.Item3);
+                        var allstock = Aggregate(holdingrows, holdingcolumns, bHasStyle, false, x => x.Units, newrow, x => x.AllStock);
+                        var orderrows = GetRows(orders.Rows, ordercolumns, key.Item1, key.Item2, key.Item3);
+                        var onorder = Aggregate(orderrows, ordercolumns, bHasStyle, false, x => x.Qty, newrow, x => x.OnOrder);
+                        newrow.Set<StockSummary, double>(x => x.TotalStock, allstock + onorder);
+
+                        var minStock = newrow.Get<StockSummary, double>(c => c.MinimumStockLevel);
+                        var balance = MinStockBehaviour switch
                         {
-                            
-                            CoreRow newrow = table.NewRow();
-                            newrow.LoadValues(rows.First().Values);
-                            Aggregate(rows, columns, bHasStyle, true, x => x.BillOfMaterials, newrow, x => x.BillOfMaterials);
-                            Aggregate(rows, columns, bHasStyle, true, x => x.Issued, newrow, x => x.Issued);
-                            Aggregate(rows, columns, bHasStyle, true, x => x.TotalRequired, newrow, x => x.TotalRequired);
-                            var nettrequired = Aggregate(rows, columns, bHasStyle, true, x => x.NettRequired, newrow, x => x.NettRequired);
-                            var holdingrows = GetRows(holdings.Rows, holdingcolumns, key.Item1, key.Item2, key.Item3);
-                            var allstock = Aggregate(holdingrows, holdingcolumns, bHasStyle, false, x => x.Units, newrow, x => x.AllStock);
-                            var orderrows = GetRows(orders.Rows, ordercolumns, key.Item1, key.Item2, key.Item3);
-                            var onorder = Aggregate(orderrows, ordercolumns, bHasStyle, false, x => x.Qty, newrow, x => x.OnOrder);
-                            newrow.Set<StockSummary, double>(x => x.TotalStock, allstock + onorder);
-                            newrow.Set<StockSummary, double>(x => x.BalanceAvailable, allstock + onorder - (newrow.Get<StockSummary,double>(c=>c.Product.DefaultInstance.MinimumStockLevel) + nettrequired));
-                            table.Rows.Add(newrow);
-                        }
-                        
+                            StockSummaryMinimumStockBehaviour.Cumulative => allstock + onorder -
+                                (minStock + nettrequired),
+                            StockSummaryMinimumStockBehaviour.Minimum or _ => allstock + onorder -
+                                Math.Max(minStock, nettrequired),
+                        };
+
+                        newrow.Set<StockSummary, double>(x => x.BalanceAvailable, balance);
+                        table.Rows.Add(newrow);
                     }
+                    
                 }
-                action?.Invoke(table, e);
-            });
-                
-        }
+            }
+            action?.Invoke(table, e);
+        });
+            
+    }
 
-        protected override bool FilterRecord(CoreRow row)
-        {
-            var result = base.FilterRecord(row);
-            if (result)
-                result = (result && (
-                    row.Get<StockSummary, double>(x => x.BillOfMaterials) != 0.0F
-                    || row.Get<StockSummary, double>(x => x.Product.DefaultInstance.MinimumStockLevel) != 0.0F
-                    || row.Get<StockSummary, double>(x => x.Issued) != 0.0F 
-                    || row.Get<StockSummary, double>(x => x.AllStock) != 0.0F 
-                    || row.Get<StockSummary, double>(x => x.OnOrder) != 0.0F
-                    )
-                );
-            return result;
-        }
+    protected override bool FilterRecord(CoreRow row)
+    {
+        var result = base.FilterRecord(row);
+        if (result)
+            result = (result && (
+                row.Get<StockSummary, double>(x => x.BillOfMaterials) != 0.0F
+                || row.Get<StockSummary, double>(x => x.MinimumStockLevel) != 0.0F
+                || row.Get<StockSummary, double>(x => x.Issued) != 0.0F 
+                || row.Get<StockSummary, double>(x => x.AllStock) != 0.0F 
+                || row.Get<StockSummary, double>(x => x.OnOrder) != 0.0F
+                )
+            );
+        return result;
+    }
 
-        // private String _minstock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Product.MinimumStockLevel, ".");
-        // private String _bom = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".");
-        // private String _issued = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".");
-        // private String _nettrequired = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".");
-        // private String _stock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".");
-        // private String _ordered = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".");
-        private string _balance = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".");
+    // private String _minstock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Product.MinimumStockLevel, ".");
+    // private String _bom = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".");
+    // private String _issued = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".");
+    // private String _nettrequired = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".");
+    // private String _stock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".");
+    // private String _ordered = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".");
+    private string _balance = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".");
 
-        protected override Brush? GetCellBackground(CoreRow row, string columnname)
+    protected override Brush? GetCellBackground(CoreRow row, string columnname)
+    {
+        
+        if (String.Equals(columnname, _balance))
         {
-            
-            if (String.Equals(columnname, _balance))
-            {
-                return row.Get<StockSummary, double>(x => x.BalanceAvailable) < 0.0F
-                    ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
-                    : null;
-            }
-            return null;
+            return row.Get<StockSummary, double>(x => x.BalanceAvailable) < 0.0F
+                ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
+                : null;
         }
+        return null;
+    }
 
-        #region IDataModelSource
-        
-        public event DataModelUpdateEvent? OnUpdateDataModel;
+    #region IDataModelSource
+    
+    public event DataModelUpdateEvent? OnUpdateDataModel;
 
-        public string SectionName => "Stock Summary";
+    public string SectionName => "Stock Summary";
 
-        public DataModel DataModel(Selection selection)
-        {
-            return new AutoDataModel<StockSummary>(null);
-        }
-        
-        #endregion
+    public DataModel DataModel(Selection selection)
+    {
+        return new AutoDataModel<StockSummary>(null);
     }
+    
+    #endregion
 }

+ 33 - 32
prs.desktop/Panels/StockSummary/StockSummaryPanel.cs

@@ -4,43 +4,44 @@ using Comal.Classes;
 using InABox.Configuration;
 using InABox.Core;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class StockSummaryPanel : StockSummaryControl, IPanel<JobMaterial>, IPropertiesPanel<StockSummaryPanelSettings, CanConfigureProductsPanels>
 {
-    public class StockSummaryPanel : StockSummaryControl, IPanel<JobMaterial>
+
+    public StockSummaryPanel()
     {
+        SectionName = nameof(StockSummaryPanel);
+        LoadSettings += (sender) => new UserConfiguration<StockSummaryProperties>(SectionName).Load();
+        SaveSettings += (sender, properties) => new UserConfiguration<StockSummaryProperties>(SectionName).Save(properties);
+    }
 
-        public StockSummaryPanel()
-        {
-            SectionName = nameof(StockSummaryPanel);
-            LoadSettings += (sender) => new UserConfiguration<StockSummaryProperties>(SectionName).Load();
-            SaveSettings += (sender, properties) => new UserConfiguration<StockSummaryProperties>(SectionName).Save(properties);
-        }
+    public bool IsReady { get; set; }
+    
+    public void CreateToolbarButtons(IPanelHost host)
+    {
+       
+    }
 
-        public bool IsReady { get; set; }
-        
-        public void CreateToolbarButtons(IPanelHost host)
-        {
-           
-        }
-
-        public Dictionary<string, object[]> Selected()
-        {
-            return new Dictionary<string, object[]>();
-        }
-
-        public void Heartbeat(TimeSpan time)
-        {
-            
-        }
-
-        public string SectionName { get; }
+    public Dictionary<string, object[]> Selected()
+    {
+        return new Dictionary<string, object[]>();
+    }
+
+    public void Heartbeat(TimeSpan time)
+    {
         
-        public DataModel DataModel(Selection selection)
-        {
-            //throw new NotImplementedException();
-            return new AutoDataModel<JobMaterial>(null);
-        }
+    }
+
+    public string SectionName { get; }
 
-        public event DataModelUpdateEvent? OnUpdateDataModel;
+    StockSummaryPanelSettings IPropertiesPanel<StockSummaryPanelSettings>.Properties { get; set; }
+
+    public DataModel DataModel(Selection selection)
+    {
+        //throw new NotImplementedException();
+        return new AutoDataModel<JobMaterial>(null);
     }
+
+    public event DataModelUpdateEvent? OnUpdateDataModel;
 }

+ 15 - 0
prs.desktop/Panels/StockSummary/StockSummaryPanelSettings.cs

@@ -0,0 +1,15 @@
+using InABox.Configuration;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSDesktop;
+
+public class StockSummaryPanelSettings : BaseObject, IGlobalConfigurationSettings
+{
+    [EnumLookupEditor(typeof(StockSummaryMinimumStockBehaviour))]
+    public StockSummaryMinimumStockBehaviour MinimumStockBehaviour { get; set; } = StockSummaryMinimumStockBehaviour.Cumulative;
+}