Browse Source

Added Cost property to ConvertDimensions() script
Improved Stock Forecast Grid
Implemented MemoryPackFormatter to RPC Transports

frogsoftware 4 months ago
parent
commit
7f2102d9c1

+ 8 - 2
prs.classes/EnclosedEntities/Dimensions/DimensionUnit.cs

@@ -66,17 +66,23 @@ namespace Comal.Classes
                 "\n"+
                 "public class Module\n"+
                 "{\n"+
-                "    public Double Quantity { get; set; }\n"+
+                "    public double Quantity { get; set; }\n"+
+                "    public double Cost { get; set; }\n"+
                 "    public IDimensions Dimensions { get; set; }\n"+
                 "\n" +
-                "    public void " + ConvertDimensionsMethodName() + "()\n"+
+                "    public bool " + ConvertDimensionsMethodName() + "()\n"+
                 "    {\n"+
                 "        // Update Dimensions and Quantity here\n"+
                 "        // eg. the following will explode a pack into its component parts:\n"+
                 "        // \n"+
                 "        // var qty = Dimensions.Quantity;\n"+
+                "        // if (qty.IsEffectivelyEqual(0.0))\n"+
+                "        //     return false;\n"+
+                "        // \n"+
                 "        // Dimensions.Quantity = 1;\n"+
                 "        // Quantity *= qty;\n"+
+                "        // Cost /= qty;\n"+
+                "        // return true;\n"+
                 "    }\n"+
                 "}\n";
         }

+ 17 - 11
prs.desktop/Grids/ProductDimensionUnitGrid.cs

@@ -125,17 +125,23 @@ public class ProductDimensionUnitGrid : DynamicDataGrid<ProductDimensionUnit>
                             progress.Report($"Updating Stock Movements: {(double)nProcessed / (double)nTotal * 100:F0}%");
                         }
                         var mvt = rows[i].ToObject<StockMovement>();
-                        var qty = DimensionUtils.ConvertDimensions(mvt.Dimensions, mvt.Units, Client<ProductDimensionUnit>.Provider);
-                        if(qty >= 0)
-                        {
-                            mvt.Received = qty;
-                            mvt.Issued = 0;
-                        }
-                        else
-                        {
-                            mvt.Received = 0;
-                            mvt.Issued = -qty;
-                        }
+                        
+                        if (mvt.Received.IsEffectivelyGreaterThan(0.0))
+                            mvt.ConvertDimensions(
+                                x=>x.Dimensions, 
+                                x=>x.Received, 
+                                x=>x.Cost, 
+                                Client<ProductDimensionUnit>.Provider
+                            );
+                        
+                        else if (mvt.Issued.IsEffectivelyGreaterThan(0.0))
+                            mvt.ConvertDimensions(
+                                x=>x.Dimensions, 
+                                x=>x.Issued, 
+                                x=>x.Cost, 
+                                Client<ProductDimensionUnit>.Provider
+                            );
+
                         if (mvt.IsChanged())
                         {
                             results.Add(mvt);

+ 31 - 28
prs.desktop/Integrations/Common/AWGMappingWindowViewModel.cs

@@ -15,6 +15,7 @@ using InABox.Integration.Awg;
 using InABox.WPF;
 using Inflector;
 using Microsoft.Xaml.Behaviors.Core;
+using PRSDimensionUtils;
 using sun.text.resources.ro;
 
 namespace PRSDesktop.Integrations.Common;
@@ -1144,21 +1145,22 @@ public class AWGMappingWindowViewModel : DependencyObject
         {
             foreach (var gasket in gaskets)
             {
-                var gasketmapping = GasketMappings?.FirstOrDefault(x => x.Code == gasket.Code);
-                if (gasketmapping != null && productCallback is not null)
+                var _mapping = GasketMappings?.FirstOrDefault(x => x.Code == gasket.Code);
+                if (_mapping != null && productCallback is not null)
                 {
-                    var totalcost = gasket.Quantity * gasket.Cost;
-                    var length = PRSDimensionUtils.DimensionUtils.ConvertDimensions(
-                        gasketmapping.Dimensions,
-                        gasketmapping.Quantity, 
-                        (f,c) => Client.Query(f,c)
+                    _mapping.ConvertDimensions(
+                        x => x.Dimensions, 
+                        x => x.Quantity, 
+                        x => x.Cost,
+                        Client<ProductDimensionUnit>.Provider
                     );
+                    
                     productCallback(
-                        gasketmapping.Entity,
+                        _mapping.Entity,
                         null,
-                        new RawDimensions() { Length = gasketmapping.Dimensions.Length },
-                        length,
-                        totalcost);
+                        new RawDimensions() { Length = _mapping.Dimensions.Length },
+                        _mapping.Quantity,
+                        _mapping.Quantity * _mapping.Cost);
                 }
             }
         }
@@ -1167,21 +1169,22 @@ public class AWGMappingWindowViewModel : DependencyObject
         {
             foreach (var component in components)
             {
-                var componentmapping = ComponentMappings?.FirstOrDefault(x => string.Equals(x.Code, component.Code));
-                if (componentmapping != null && productCallback is not null)
+                var _mapping = ComponentMappings?.FirstOrDefault(x => string.Equals(x.Code, component.Code));
+                if (_mapping != null && productCallback is not null)
                 {
-                    var totalcost = component.Cost * componentmapping.Quantity;
-                    var qty = PRSDimensionUtils.DimensionUtils.ConvertDimensions(
-                        componentmapping.Dimensions,
-                        componentmapping.Quantity, 
-                        (f,c) => Client.Query(f,c)
+                    _mapping.ConvertDimensions(
+                        x => x.Dimensions, 
+                        x => x.Quantity, 
+                        x => x.Cost,
+                        Client<ProductDimensionUnit>.Provider
                     );
+                    
                     productCallback(
-                        componentmapping.Entity,
+                        _mapping.Entity,
                         null,
-                        new RawDimensions() { Quantity = componentmapping.Dimensions.Quantity },
-                        qty,
-                        totalcost);
+                        new RawDimensions() { Quantity = _mapping.Dimensions.Quantity },
+                        _mapping.Quantity,
+                        _mapping.Quantity * _mapping.Cost);
                 }
             }
         }
@@ -1190,11 +1193,11 @@ public class AWGMappingWindowViewModel : DependencyObject
         {
             foreach (var glass in glasses)
             {
-                var glassmapping = GlassMappings?.FirstOrDefault(x => string.Equals(x.Code, glass.Code));
-                if (glassmapping != null && productCallback is not null)
+                var _mapping = GlassMappings?.FirstOrDefault(x => string.Equals(x.Code, glass.Code));
+                if (_mapping != null && productCallback is not null)
                 {
                     productCallback(
-                        glassmapping.Entity,
+                        _mapping.Entity,
                         null,
                         new RawDimensions() { Height = glass.Height, Width = glass.Width },
                         glass.Quantity,
@@ -1207,11 +1210,11 @@ public class AWGMappingWindowViewModel : DependencyObject
         {
             foreach (var activity in labour)
             {
-                var activitymapping = LabourMappings?.FirstOrDefault(x => string.Equals(x.Code, activity.Code));
-                if (activitymapping != null && labourCallback is not null)
+                var _mapping = LabourMappings?.FirstOrDefault(x => string.Equals(x.Code, activity.Code));
+                if (_mapping != null && labourCallback is not null)
                 {
                     labourCallback(
-                        activitymapping.Entity,
+                        _mapping.Entity,
                         TimeSpan.FromHours(activity.Quantity),
                         activity.Quantity * activity.Cost);
                 }

+ 0 - 2
prs.desktop/Integrations/Logikal/LogikalClient.cs

@@ -1,13 +1,11 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
-using System.ComponentModel;
 using System.Data;
 using System.Data.SQLite;
 using System.Diagnostics;
 using System.IO;
 using System.Linq;
-using System.Text.RegularExpressions;
 using System.Threading;
 using System.Windows.Forms;
 using Comal.Classes;

+ 1 - 1
prs.desktop/PRSDesktop.csproj

@@ -887,6 +887,7 @@
       <ProjectReference Include="..\..\InABox\InABox.DatabaseProxy\InABox.DatabaseProxy.csproj" />
       <ProjectReference Include="..\..\InABox\InABox.Database\InABox.Database.csproj" />
       <ProjectReference Include="..\..\InABox\inabox.dxf\InABox.Dxf.csproj" />
+      <ProjectReference Include="..\..\InABox\InABox.Formatters.MemoryPack\InABox.Formatters.MemoryPack.csproj" />
       <ProjectReference Include="..\..\InABox\InABox.Integration.V6\InABox.Integration.V6.csproj" />
       <ProjectReference Include="..\..\InABox\InABox.Logging\InABox.Logging.csproj" />
       <ProjectReference Include="..\..\inabox\InABox.Integration.Logikal\InABox.Integration.Logikal.csproj" />
@@ -908,7 +909,6 @@
       <PackageReference Include="DocumentFormat.OpenXml" Version="3.3.0" />
       <PackageReference Include="Fluent.Ribbon" Version="10.1.0" />
       <PackageReference Include="gmaps-api-net" Version="0.33.0" />
-      <PackageReference Include="H.Formatters.Newtonsoft.Json" Version="15.0.0" />
       <PackageReference Include="Microsoft.Office.Interop.Outlook" Version="15.0.4797.1004" />
       <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
       <PackageReference Include="MotorolaCoreScannerNetStandard" Version="1.1.5" />

+ 15 - 15
prs.desktop/Panels/Reservation Management/ReservationManagementItemGrid.cs

@@ -370,7 +370,15 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
                 jri.Style.CopyFrom(new ProductStyle());
             }
 
-            var qtyRequired = DimensionUtils.ConvertDimensions(jri.Dimensions, Math.Max(jri.Qty - jri.InStock - jri.OnOrder, 0.0), (f,c) => Client.Query(f,c));
+            var _dimensions = jri.Dimensions;
+            var _qtyRequired = Math.Max(jri.Qty - jri.InStock - jri.OnOrder, 0.0);
+            var _cost = jri.UnitCost;
+            
+            DimensionUtils.ConvertDimensions(
+                jri.Dimensions, 
+                ref _qtyRequired,
+                ref _cost, 
+                Client<ProductDimensionUnit>.Provider);
 
             var item = items.FirstOrDefault(x =>
                 x.Product.ID == jri.Product.ID
@@ -383,8 +391,8 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
                 items.Add(item);
             }
 
-            item.RequiredQuantity += qtyRequired;
-            item.SetRequiredQuantity(jri.Job.ID, jri.ID, $"{jri.Job.JobNumber}-{jri.Requisition.Number}", qtyRequired);
+            item.RequiredQuantity += _qtyRequired;
+            item.SetRequiredQuantity(jri.Job.ID, jri.ID, $"{jri.Job.JobNumber}-{jri.Requisition.Number}", _qtyRequired);
         }
 
         var window = new StockForecastOrderScreen(items, false) { Owner = Application.Current.MainWindow };
@@ -502,14 +510,8 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
                     double unitCost = _pljri.UnitCost;
                     var dimensions = _pljri.Dimensions.Copy();
                     if (!String.IsNullOrWhiteSpace(_pljri.Dimensions.Unit.Conversion))
-                    {
-                        var qty = DimensionUtils.ConvertDimensions(dimensions, _pljri.Qty, Client<ProductDimensionUnit>.Provider);
-                        if (!qty.IsEffectivelyEqual(_pljri.Qty))
-                        {
-                            unitCost = _pljri.UnitCost * _pljri.Qty / (qty.IsEffectivelyEqual(0.0) ? 1.0 : qty);
-                            qtyrequired = qty;
-                        }
-                    }
+                        DimensionUtils.ConvertDimensions(dimensions, ref qtyrequired, ref unitCost, Client<ProductDimensionUnit>.Provider);
+                    
                     var _locations = _movementquery.Result.Where(x => x.JobRequisitionItem.ID == _pljri.ID)
                         .GroupBy(x => x.Location.ID);
                     foreach (var _location in _locations)
@@ -709,10 +711,8 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
         {
             Progress.ShowModal("Standardising Dimensions", progress =>
             {
-                var qty = jri.Qty;
-                DimensionUtils.ConvertDimensions(jri.Dimensions, ref qty, (f, c) => Client.Query(f, c));
-                jri.Qty = qty;
-                Client.Save(jri, "Converted Dimensions in Reservation Management Screen");
+                if (jri.ConvertDimensions(x => x.Dimensions, x => x.Qty, x => x.UnitCost, Client<ProductDimensionUnit>.Provider))
+                    Client.Save(jri, "Converted Dimensions in Reservation Management Screen");
             });
             Refresh(false,true);
 

+ 4 - 3
prs.desktop/Panels/Stock Forecast/OrderScreen/StockForecastOrderingGrid.cs

@@ -726,9 +726,10 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockOrderingItem>
 
         var d = new StockDimensions();
         d.CopyFrom(supplierProduct.Dimensions);
-        var result = DimensionUtils.ConvertDimensions(d, 1.0, (f,c) => Client.Query(f,c));
-        req /= result;
-        return req;
+        var qtyreq = 1.0;
+        var cost = -.0;
+        DimensionUtils.ConvertDimensions(d, ref qtyreq, ref cost, (f,c) => Client.Query(f,c));
+        return qtyreq;
     }
 
     private SupplierProduct? SelectSupplierProduct(IEnumerable<SupplierProduct> supplierProducts, StockOrderingItem item)

+ 121 - 30
prs.desktop/Panels/Stock Forecast/StockForecastGrid.cs

@@ -172,13 +172,13 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
             Position = DynamicActionColumnPosition.Start
         });
         
-        var _averageColumn = CreateColumn(GetAverage, ColumnTag.Average,"Avg.","F2");
+        var _averageColumn = CreateColumn(GetAverage, ColumnTag.Average,"Avg.","");
         _averageColumn.ContextMenu = Average_Menu;
 
-        var _periodColumn = CreateColumn(GetPeriod, ColumnTag.Period, "Days", "F0");
-        _periodColumn.ContextMenu = MinimumStock_Menu;
+        var _periodColumn = CreateColumn(GetPeriod, ColumnTag.Period, "Days", "");
+        _periodColumn.ContextMenu = MinimumDays_Menu;
         
-        var _minStockColumn = CreateColumn(GetMinimumStockLevel, ColumnTag.MinimumStockRequired,"Min.","F2");
+        var _minStockColumn = CreateColumn(GetMinimumStockLevel, ColumnTag.MinimumStockRequired,"Min.","");
         _minStockColumn.ContextMenu = MinimumStock_Menu;
         CreateColumn(GetGeneralStockLevel, ColumnTag.GeneralStockHoldings,"Hld.","F2");
         CreateColumn(GetGeneralPurchaseOrder, ColumnTag.GeneralPurchaseOrders, "PO.","F2");
@@ -294,7 +294,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 var item = Grid.LoadItem(row);
 
                 var periods = (Parent as StockForecastGrid)?.Properties?.AveragePeriods ?? 1;
-                var avg = item.MinStock.IsEffectivelyGreaterThan(0.0) && item.MinStockDays > 0
+                var avg = item.MinStockDays > 0
                     ? !item.MinStock.IsEffectivelyLessThan(item.Average)
                         ? Colors.LightGreen.ToBrush(0.5)
                         : Colors.LightSalmon.ToBrush(0.5)
@@ -399,12 +399,37 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
     private DynamicTextColumn GetColumn(ColumnTag tag) => (ActionColumns.First(x => Equals(x.Tag, tag)) as DynamicTextColumn)!;
 
     private object GetAverage(CoreRow? row)
-        => row is not null ? LoadItem(row).Average : 0.0;
+    {
+        if (row is not null)
+        {
+            var item = LoadItem(row);
+            if (item.IsProductInstance)
+                return $"{item.Average:F2}";
+        }
+        return "";
+    }
 
     private object GetPeriod(CoreRow? row)
-        => row is not null ? LoadItem(row).MinStockDays : 0.0;
-    
-    private object GetMinimumStockLevel(CoreRow? row) => row is not null ? LoadItem(row).MinStock : 0.0;
+    {
+        if (row is not null)
+        {
+            var item = LoadItem(row);
+            if (item.IsProductInstance)
+                return $"{item.MinStockDays}";
+        }
+        return "";
+    }
+
+    private object GetMinimumStockLevel(CoreRow? row)
+    {
+        if (row is not null)
+        {
+            var item = LoadItem(row);
+            if (item.IsProductInstance)
+                return $"{item.MinStock}";
+        }
+        return "";
+    }
 
     private object GetGeneralStockLevel(CoreRow? row)
         => row is not null ? LoadItem(row).GenStock : 0.0;
@@ -518,6 +543,30 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
 
         switch (tag)
         {
+            case ColumnTag.Average :
+                ShowDetailGrid(
+                    "Stock Usage", 
+                    () => BuildDetailGrid<StockMovement>(
+                        ColumnTag.Average.ToString(), 
+                        x => x.Product.ID, 
+                        item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        null,
+                        new Filter<StockMovement>(x=>x.Job.ID).IsEqualTo(Guid.Empty)
+                            .And(x=>x.Date).IsGreaterThanOrEqualTo(DateTime.Today.AddDays(0-item.MinStockDays * this.Properties.AveragePeriods))
+                            .And(x=>x.Type).InList(
+                                StockMovementType.Issue, 
+                                StockMovementType.TransferOut, 
+                                StockMovementType.TransferIn, 
+                                StockMovementType.StockTake),
+                        null
+                    )
+                );               
+
+                break;
             case ColumnTag.GeneralStockHoldings:
                 ShowDetailGrid(
                     "Stock Holdings", 
@@ -644,6 +693,37 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
         }
     }
     
+    private ContextMenu? MinimumDays_Menu(CoreRow[]? rows)
+    {
+        if (rows is null || rows.Length == 0) return null;
+        var items = LoadItems(rows);
+        
+        var menu = new ContextMenu();
+        if (Security.CanEdit<ProductInstance>())
+        {
+            if(items.Any(x => x.IsProductInstance))
+            {
+                menu.AddItem("Adjust minimum stock days", null, items, AdjustMinStockDays_Click);
+            }
+        }
+        if(menu.Items.Count > 0)
+            return menu;
+        return null;
+    }
+
+    private void AdjustMinStockDays_Click(StockForecastItem[] sfItems)
+    {
+        var items = sfItems.SelectMany(x=>x.ProductInstances).ToArray();
+        var dayValues = items.Select(x => x.MinimumStockDays).Distinct().ToArray();
+        int days = dayValues.Length == 1 ? dayValues[0] : 0;
+        if (NumberEdit.Execute("Number of Days", 0, int.MaxValue, ref days))
+        {
+            foreach (var item in items)
+                item.MinimumStockDays = days;
+            Client.Save(items.Where(x=>x.IsChanged()),"Minimum Stock Days adjusted");
+            Refresh(false,true);
+        }
+    }
 
     private ContextMenu? MinimumStock_Menu(CoreRow[]? rows)
     {
@@ -660,13 +740,8 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
             }
         }
         if(menu.Items.Count > 0)
-        {
             return menu;
-        }
-        else
-        {
-            return null;
-        }
+        return null;
     }
 
     private void AdjustMinStock_Click(StockForecastItem[] items)
@@ -954,7 +1029,11 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
             ),
             
             GetQuery<StockMovement>(
-                filter: new Filter<StockMovement>(x => x.Type).InList(StockMovementType.Issue, StockMovementType.StockTake),
+                filter: new Filter<StockMovement>(x => x.Type).InList(
+                    StockMovementType.Issue, 
+                    StockMovementType.TransferOut, 
+                    StockMovementType.TransferIn, 
+                    StockMovementType.StockTake),
                 columns: Columns.None<StockMovement>()
                     .Add(x=>x.Date)
                     .Add(x => x.Units)
@@ -999,7 +1078,9 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 {
                     var dimensions = new StockDimensions();
                     dimensions.CopyFrom(instance.Dimensions);
-                    var minStock = DimensionUtils.ConvertDimensions(dimensions, instance.MinimumStockLevel, (f,c) => Client.Query(f,c));
+                    var minStock = (double)instance.MinimumStockLevel;
+                    var minCost = instance.AverageCost;
+                    DimensionUtils.ConvertDimensions(dimensions, ref minStock, ref minCost, Client<ProductDimensionUnit>.Provider);
 
                     var item = GetItem(new(instance.Product.ID, instance.Style.ID, dimensions));
                     item.MinStockDays = instance.MinimumStockDays;
@@ -1011,7 +1092,9 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 foreach(var holdingrow in holdings.Rows)
                 {
                     var holding = holdingrow.ToObject<StockHolding>();
-                    holding.Units = DimensionUtils.ConvertDimensions(holding.Dimensions, holding.Units, (f,c) => Client.Query(f,c));
+                    holding.ConvertDimensions(x => x.Dimensions, x => x.Units, x => x.AverageValue,
+                        Client<ProductDimensionUnit>.Provider);
+                    //holding.Units = DimensionUtils.ConvertDimensions(holding.Dimensions, holding.Units, (f,c) => Client.Query(f,c));
 
                     var item = GetItem(GetKey(holding.Product.ID, holding.Style.ID, holding.Dimensions));
                     if(holding.Job.ID == Guid.Empty)
@@ -1029,15 +1112,19 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     .ToDictionary(x => x.ID);
                 foreach (var poi in purchaseOrderItems.Values)
                 {
-                    poi.Qty = DimensionUtils.ConvertDimensions(poi.Dimensions, poi.Qty, (f,c) => Client.Query(f,c));
+                    poi.ConvertDimensions(x => x.Dimensions, x => x.Qty, x => x.Cost,
+                        Client<ProductDimensionUnit>.Provider);
+                    //poi.Qty = DimensionUtils.ConvertDimensions(poi.Dimensions, poi.Qty, (f,c) => Client.Query(f,c));
                 }
                 
                 var allocations = results.Get<PurchaseOrderItemAllocation>();
                 foreach(var allocationrow in allocations.Rows)
                 {
                     var allocation  = allocationrow.ToObject<PurchaseOrderItemAllocation>();
+                    var d = allocation.Item.Dimensions.Copy();
                     var q = allocation.Quantity;
-                    DimensionUtils.ConvertDimensions(allocation.Item.Dimensions, ref q, (f,c) => Client.Query(f,c));
+                    var c = allocation.Item.Cost;
+                    DimensionUtils.ConvertDimensions(d, ref q, ref c, Client<ProductDimensionUnit>.Provider);
                     // POIAs are already converted where necessary, so we don't have to update the quantities again, just update the dimensions
                     
                     var key = new ItemKey(
@@ -1083,7 +1170,8 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 foreach(var bomItemRow in jobBOMItems.Rows)
                 {
                     var bomItem = bomItemRow.ToObject<JobBillOfMaterialsItem>();
-                    bomItem.Quantity = DimensionUtils.ConvertDimensions(bomItem.Dimensions, bomItem.Quantity, (f,c) => Client.Query(f,c));
+                    bomItem.ConvertDimensions(x => x.Dimensions, x => x.Quantity, x => x.UnitCost, Client<ProductDimensionUnit>.Provider);
+                    //bomItem.Quantity = DimensionUtils.ConvertDimensions(bomItem.Dimensions, bomItem.Quantity, (f,c) => Client.Query(f,c));
                     var key = GetKey(bomItem.Product.ID, bomItem.Style.ID, bomItem.Dimensions);
                     var item = GetItem(key);
 
@@ -1094,16 +1182,19 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 foreach(var mvtrow in movements.Rows)
                 {
                     var movement = mvtrow.ToObject<StockMovement>();
-                    movement.Units = DimensionUtils.ConvertDimensions(movement.Dimensions, movement.Units, (f,c) => Client.Query(f,c));
-                    
+                    movement.ConvertDimensions(x => x.Dimensions, x => x.Units, x => x.Cost,
+                        Client<ProductDimensionUnit>.Provider);
+                    //movement.Units = DimensionUtils.ConvertDimensions(movement.Dimensions, movement.Units, (f,c) => Client.Query(f,c));
+
                     var item = GetItem(GetKey(movement.Product.ID, movement.Style.ID, movement.Dimensions));
-                    
-                    var periods = (Properties?.AveragePeriods ?? 1);
-                    var days = item.MinStockDays * periods;
-                    if (days != 0 && DateTime.Today.Subtract(movement.Date) <= TimeSpan.FromDays(days))
-                        item.Average += (0.0 - movement.Units)/periods;
-                    
-                    if(movement.Job.ID != Guid.Empty)
+                    if (movement.Job.ID == Guid.Empty)
+                    {
+                        var periods = (Properties?.AveragePeriods ?? 1);
+                        var days = item.MinStockDays * periods;
+                        if (days != 0 && DateTime.Today.Subtract(movement.Date) <= TimeSpan.FromDays(days))
+                            item.Average += (0.0 - movement.Units) / periods;
+                    }
+                    else
                         item.AddJobBOM(movement.Job.ID, movement.Units);
                 }
 

+ 7 - 0
prs.desktop/Panels/Stock Forecast/StockForecastProductGroupTree.cs

@@ -20,6 +20,13 @@ public class StockForecastProductGroupTree : DynamicSelectorGrid<ProductGroup>,
     {
         ColumnsTag = "StockForecastProductGroupSelector";
         _expandorcollapseAll = AddButton("Collapse All", null, ExpandOrCollapseAll);
+        
+        var issuesColumn = ActionColumns.OfType<DynamicProblemsColumn<ProductGroup>>().FirstOrDefault();
+        if(issuesColumn is not null)
+        {
+            issuesColumn.Position = DynamicActionColumnPosition.Hidden;
+        }
+        
     }
 
     private bool ExpandOrCollapseAll(Button button, CoreRow[] rows)

+ 1 - 1
prs.desktop/prsdesktop.iss

@@ -8,7 +8,7 @@
 #define public Dependency_Path_NetCoreCheck "dependencies\"
 
 #define MyAppName "PRS Desktop"
-#define MyAppVersion "8.46"
+#define MyAppVersion "8.46a"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSDesktop.exe"

+ 3 - 2
prs.licensing/GUI/Console.xaml.cs

@@ -17,6 +17,7 @@ using InABox.Rpc;
 using PRSServer;
 using Comal.Classes;
 using H.Formatters;
+using InABox.Formatters;
 using InABox.Wpf;
 using Microsoft.Win32;
 using PRS.Shared;
@@ -170,8 +171,8 @@ public partial class Console : Window, INotifyPropertyChanged
         if (_creatingClient) return;
 
         _creatingClient = true;
-
-        var client = new PipeClient<string>(GetPipeName(), new MessagePackFormatter<string>());
+        
+        var client = new PipeClient<string>(GetPipeName(), new MemoryPackFormatter<string>());
         client.MessageReceived += (o, args) =>
         {
             Dispatcher.BeginInvoke(() =>

+ 2 - 2
prs.licensing/GUI/ServerConsole.cs

@@ -13,6 +13,7 @@ using System.Windows.Threading;
 using Comal.Classes;
 using H.Formatters;
 using InABox.Core;
+using InABox.Formatters;
 
 namespace PRSLicensing;
 
@@ -37,8 +38,7 @@ public class LicensingConsole : InABox.Wpf.Console.Console
     protected override void OnLoaded()
     {
         base.OnLoaded();
-
-        _client = new PipeClient<string>(ServiceName, new MessagePackFormatter<string>());
+        _client = new PipeClient<string>(ServiceName, new MemoryPackFormatter<string>());
         _client.MessageReceived += (o, args) =>
         {
             Dispatcher.BeginInvoke(() =>

+ 1 - 1
prs.licensing/PRSLicensing.csproj

@@ -26,13 +26,13 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
-    <PackageReference Include="H.Formatters.BinaryFormatter" Version="15.0.0" />
     <PackageReference Include="PropertyChanged.Fody" Version="4.1.0" />
     <PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.0" />
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\..\inabox\InABox.Client.RPC\InABox.Client.RPC.csproj" />
+    <ProjectReference Include="..\..\InABox\InABox.Formatters.MemoryPack\InABox.Formatters.MemoryPack.csproj" />
     <ProjectReference Include="..\..\inabox\inabox.wpf\InABox.Wpf.csproj" />
     <ProjectReference Include="..\prs.services\PRSServices.csproj" />
     <ProjectReference Include="..\prs.shared\PRS.Shared.csproj" />

+ 1 - 1
prs.licensing/PRSLicensing.iss

@@ -8,7 +8,7 @@
 #define public Dependency_Path_NetCoreCheck "dependencies\"
 
 #define MyAppName "PRS Licensing"
-#define MyAppVersion "8.46"
+#define MyAppVersion "8.46a"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSLicensing.exe"

+ 3 - 1
prs.server/Forms/Console/ServerConsole.cs

@@ -11,6 +11,7 @@ using System.Windows;
 using System.Windows.Media;
 using System.Windows.Threading;
 using H.Formatters;
+using InABox.Formatters;
 using Console = InABox.Wpf.Console.Console;
 
 namespace PRSServer;
@@ -36,8 +37,9 @@ public class ServerConsole : Console
     protected override void OnLoaded()
     {
         base.OnLoaded();
+        
+        _client = new PipeClient<string>(ServiceName, new MemoryPackFormatter<string>());
 
-        _client = new PipeClient<string>(ServiceName, new MessagePackFormatter<string>());
         _client.MessageReceived += (o, args) =>
         {
             Dispatcher.BeginInvoke(() =>

+ 1 - 0
prs.server/PRSServer.csproj

@@ -116,6 +116,7 @@
         <ProjectReference Include="..\..\inabox\InABox.Client.RPC\InABox.Client.RPC.csproj" />
         <ProjectReference Include="..\..\InABox\InABox.Database.SQLite\InABox.Database.SQLite.csproj" />
         <ProjectReference Include="..\..\InABox\InABox.Database\InABox.Database.csproj" />
+        <ProjectReference Include="..\..\InABox\InABox.Formatters.MemoryPack\InABox.Formatters.MemoryPack.csproj" />
         <ProjectReference Include="..\..\InABox\inabox.logging.shared\InABox.Logging.Shared.csproj">
             <Private>True</Private>
         </ProjectReference>

+ 1 - 1
prs.server/PRSServer.iss

@@ -8,7 +8,7 @@
 #define public Dependency_Path_NetCoreCheck "dependencies\"
 
 #define MyAppName "PRS Server"
-#define MyAppVersion "8.46"
+#define MyAppVersion "8.46a"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSServer.exe"

+ 8 - 7
prs.shared/Posters/Timberline/BillTimberlinePoster.cs

@@ -464,15 +464,16 @@ public class BillTimberlinePoster : ITimberlinePoster<Bill, BillTimberlineSettin
                 if (purchaseOrderItems.TryGetValue(billLine.OrderItem.ID, out var poItem))
                 {
                     var dimensions = poItem.Dimensions.Copy();
-                    var qty = DimensionUtils.ConvertDimensions(dimensions, poItem.Qty, Client<ProductDimensionUnit>.Provider);
                     var poCost = poItem.Cost;
                     var poQty = poItem.Qty;
-                    if (!qty.IsEffectivelyEqual(poItem.Qty))
-                    {
-                        poCost = poItem.Cost * poItem.Qty / (qty.IsEffectivelyEqual(0.0) ? 1.0 : qty);
-                        poQty = qty;
-                    }
-
+                    
+                    DimensionUtils.ConvertDimensions(
+                        dimensions, 
+                        ref poQty, 
+                        ref poCost,
+                        Client<ProductDimensionUnit>.Provider
+                    );
+                    
                     if(allocations.TryGetValue(billLine.OrderItem.ID, out var poias))
                     {
                         var remQty = poQty - poias.Sum(x => x.Quantity);

+ 10 - 9
prs.shared/Posters/Timberline/PurchaseOrderTimberlinePoster.cs

@@ -320,18 +320,19 @@ public class Module
                     // Get the data for all the lines.
                     foreach(var poItem in POItems)
                     {
-                        // First, we must convert the dimensions and qty on the POItem to match the "exploded" allocations. Note we need to make copies,
-                        // because we will be saving this poItem later on.
+                        // First, we must convert the dimensions and qty on the POItem to match the "exploded" allocations.
+                        // Note we need to make copies, because we will be saving this poItem later on.
                         var dimensions = poItem.Dimensions.Copy();
-                        var qty = DimensionUtils.ConvertDimensions(dimensions, poItem.Qty, Client<ProductDimensionUnit>.Provider);
                         var poCost = poItem.Cost;
                         var poQty = poItem.Qty;
-                        if (!qty.IsEffectivelyEqual(poItem.Qty))
-                        {
-                            poCost = poItem.Cost * poItem.Qty / (qty.IsEffectivelyEqual(0.0) ? 1.0 : qty);
-                            poQty = qty;
-                        }
-
+                        
+                        DimensionUtils.ConvertDimensions(
+                            dimensions, 
+                            ref poQty, 
+                            ref poCost, 
+                            Client<ProductDimensionUnit>.Provider
+                        );
+                        
                         if(!allocations.TryGetValue(poItem.ID, out var poias))
                         {
                             poias = new();

+ 66 - 33
prs.stores/Utilities/DimensionUtils.cs

@@ -1,3 +1,4 @@
+using System.Linq.Expressions;
 using Comal.Classes;
 using InABox.Core;
 using InABox.Scripting;
@@ -15,7 +16,7 @@ public static class DimensionUtils
 
         _dimensionscriptcache ??= new Dictionary<Guid, ScriptDocument>();
 
-        var scripts = reload(
+        var _scripts = reload(
             ids != null
                 ? new Filter<ProductDimensionUnit>(x => x.ID).InList(ids)
                 : null,
@@ -25,13 +26,13 @@ public static class DimensionUtils
         ).ToDictionary<ProductDimensionUnit, Guid, String>(x => x.ID, x => x.Conversion);
 
     
-        foreach (var id in scripts.Keys)
+        foreach (var _id in _scripts.Keys)
         {
-            var doc = !String.IsNullOrWhiteSpace(scripts[id]) ? new ScriptDocument(scripts[id]) : null;
-            if (doc?.Compile() == true)
-                _dimensionscriptcache[id] = doc;
+            var _doc = !String.IsNullOrWhiteSpace(_scripts[_id]) ? new ScriptDocument(_scripts[_id]) : null;
+            if (_doc?.Compile() == true)
+                _dimensionscriptcache[_id] = _doc;
             else
-                _dimensionscriptcache.Remove(id);
+                _dimensionscriptcache.Remove(_id);
         }
     }
     public static void ReloadDimensionScriptCache(Guid[]? ids, IQueryProvider<ProductDimensionUnit> query)
@@ -39,37 +40,47 @@ public static class DimensionUtils
         ReloadDimensionScriptCache(ids, (f, c) => query.Query(f, c));
     }
 
-    public static void ConvertDimensions(
-        IDimensions dimensions,
+    private static bool DoConvertDimensions(
+        IDimensions? dimensions,
         ref double quantity,
-        IQueryProvider<ProductDimensionUnit> reload) => ConvertDimensions(dimensions, ref quantity, (f, c) => reload.Query(f, c));
-    
-    public static void ConvertDimensions(
-        IDimensions dimensions,
+        ref double cost,
+        IQueryProvider<ProductDimensionUnit> reload) => DoConvertDimensions(dimensions, ref quantity, ref cost, (f, c) => reload.Query(f, c));
+
+    private static bool DoConvertDimensions(
+        IDimensions? dimensions,
         ref double quantity,
+        ref double cost,
         Func<Filter<ProductDimensionUnit>?,Columns<ProductDimensionUnit>,CoreTable> reload)
     {
+        if (dimensions == null || quantity.IsEffectivelyEqual(0.0))
+            return false;
+        
         if (_dimensionscriptcache == null)
             ReloadDimensionScriptCache(null, reload);
         
-        if (_dimensionscriptcache?.TryGetValue(dimensions.Unit.ID, out ScriptDocument? script) == true)
+        if (_dimensionscriptcache?.TryGetValue(dimensions.Unit.ID, out ScriptDocument? _script) == true)
         {
-            script.SetValue("Quantity", quantity);
-            script.SetValue("Dimensions", dimensions);
-            script.Execute("Module", DimensionUnit.ConvertDimensionsMethodName());
-            quantity = Convert.ToDouble(script.GetValue("Quantity"));
+            _script.SetValue("Quantity", quantity);
+            _script.SetValue("Cost", cost);
+            _script.SetValue("Dimensions", dimensions);
+            if (_script.Execute("Module", DimensionUnit.ConvertDimensionsMethodName()))
+            {
+                quantity = Convert.ToDouble(_script.GetValue("Quantity"));
+                cost = Convert.ToDouble(_script.GetValue("Cost"));
+                return true;
+            }
         }
+
+        return false;
     }
 
-    public static double ConvertDimensions(IDimensions dimensions, double quantity, Func<Filter<ProductDimensionUnit>?, Columns<ProductDimensionUnit>, CoreTable> reload)
+    public static bool ConvertDimensions(IDimensions dimensions, ref double quantity, ref double cost, Func<Filter<ProductDimensionUnit>?, Columns<ProductDimensionUnit>, CoreTable> reload)
     {
-        ConvertDimensions(dimensions, ref quantity, reload);
-        return quantity;
+        return DoConvertDimensions(dimensions, ref quantity, ref cost, reload);
     }
-    public static double ConvertDimensions(IDimensions dimensions, double quantity, IQueryProvider<ProductDimensionUnit> query)
+    public static bool ConvertDimensions(IDimensions dimensions, ref double quantity, ref double cost, IQueryProvider<ProductDimensionUnit> query)
     {
-        ConvertDimensions(dimensions, ref quantity, query);
-        return quantity;
+        return DoConvertDimensions(dimensions, ref quantity, ref cost, query);
     }
 
     /// <summary>
@@ -77,18 +88,40 @@ public static class DimensionUtils
     /// its <see cref="PurchaseOrderItem.Cost"/> and <see
     /// cref="PurchaseOrderItem.Qty"/> accordingly.
     /// </summary>
-    public static void ConvertDimensions(this PurchaseOrderItem entity, IQueryProvider<ProductDimensionUnit> query)
+    public static bool ConvertDimensions(this PurchaseOrderItem entity, IQueryProvider<ProductDimensionUnit> query)
+    {
+        var _dimensions = entity.Dimensions;
+        var _qty = entity.Qty;
+        var _cost = entity.Cost;
+        if (DoConvertDimensions(_dimensions, ref _qty, ref _cost, query))
+        {
+            entity.Cost = entity.Cost * entity.Qty / (_qty.IsEffectivelyEqual(0.0) ? 1.0 : _qty);
+            entity.Qty = _qty;
+            entity.Cost = _cost;
+            return true;
+        }
+        return false;
+    }
+
+    public static bool ConvertDimensions<T>(
+        this T sender,
+        Expression<Func<T,IDimensions>> dimensions,
+        Expression<Func<T, double>> quantity,
+        Expression<Func<T, double>> cost, 
+        IQueryProvider<ProductDimensionUnit> reload)
+    where T : BaseObject
     {
-        var dimensions = entity.Dimensions;
-        var qty = entity.Qty;
-        ConvertDimensions(
-            dimensions, 
-            ref qty,
-            query);
-        if (!qty.IsEffectivelyEqual(entity.Qty))
+        var _dimprop = CoreUtils.GetPropertyValue(sender, CoreUtils.GetFullPropertyName(dimensions, ".")) as IDimensions;
+        var _qtyprop = CoreUtils.GetFullPropertyName(quantity, ".");
+        var _qtyval = Convert.ToDouble(CoreUtils.GetPropertyValue(sender, _qtyprop));
+        var _costprop = CoreUtils.GetFullPropertyName(cost, ".");
+        var _costval = Convert.ToDouble(CoreUtils.GetPropertyValue(sender, _costprop));
+        if (DoConvertDimensions(_dimprop, ref _qtyval, ref _costval, reload))
         {
-            entity.Cost = entity.Cost * entity.Qty / (qty.IsEffectivelyEqual(0.0) ? 1.0 : qty);
-            entity.Qty = qty;
+            CoreUtils.SetPropertyValue(sender, _qtyprop, _qtyval);
+            CoreUtils.SetPropertyValue(sender, _costprop, _costval);
+            return true;
         }
+        return false;
     }
 }