Quellcode durchsuchen

Added MinimumStockDays to ProductInstance
Added Avergae Usage calculations to Stock Forecast Grid
Log Search screen is now available on all server engines

frogsoftware vor 4 Wochen
Ursprung
Commit
e7dc8465ca

+ 2 - 0
prs.classes/Entities/Product/Instance/IProductInstance.cs

@@ -14,5 +14,7 @@ namespace Comal.Classes
         StockDimensions Dimensions { get; set; }
 
         int MinimumStockLevel { get; set; }
+        
+        int MinimumStockDays { get; set; }
     }
 }

+ 9 - 4
prs.classes/Entities/Product/Instance/ProductInstance.cs

@@ -73,28 +73,33 @@ namespace Comal.Classes
         [LoggableProperty]
         public int MinimumStockLevel { get; set; }
 
+        [IntegerEditor]
         [EditorSequence(5)]
+        [LoggableProperty]
+        public int MinimumStockDays{ get; set; }
+        
+        [EditorSequence(6)]
         [Aggregate(typeof(ProductInstanceFreeStockAggregate))]
         [DoubleEditor(Editable = Editable.Hidden)]
         public double FreeStock { get; set; }
         
-        [EditorSequence(5)]
+        [EditorSequence(7)]
         [Aggregate(typeof(ProductInstanceFreeStockValueAggregate))]
         [DoubleEditor(Editable = Editable.Hidden)]
         public double FreeStockValue { get; set; }
         
         [CurrencyEditor(Visible = Visible.Optional)]
-        [EditorSequence(5)]
+        [EditorSequence(8)]
         [LoggableProperty]
         public double NettCost { get; set; }
         
         [Formula(typeof(ProductInstanceAverageCostFormula))]
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Disabled)]
-        [EditorSequence(6)]
+        [EditorSequence(9)]
         public double AverageCost { get; set; }
 
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Disabled)]
-        [EditorSequence(7)]
+        [EditorSequence(10)]
         [LoggableProperty]
         public double LastCost { get; set; }
 

+ 3 - 0
prs.classes/Entities/Product/Instance/ProductInstanceLink.cs

@@ -18,6 +18,9 @@ namespace Comal.Classes
 
         [IntegerEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
         public int MinimumStockLevel { get; set; }
+
+        [IntegerEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
+        public int MinimumStockDays { get; set; }
         
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
         public double AverageCost { get; set; }

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

@@ -86,6 +86,13 @@ public partial class StockForecastControl : UserControl
 
         StockForecastGrid.JobIDs = Properties.Jobs;
         StockForecastGrid.SupplierIDs = Properties.Suppliers.ToHashSet();
+        StockForecastGrid.Properties = Properties;
+        StockForecastGrid.OnPropertiesChanged += (o,e) =>
+        {
+            DoSaveSettings();
+        };
+
+        
     }
 
     public void Shutdown(CancelEventArgs? cancel)

+ 101 - 22
prs.desktop/Panels/Stock Forecast/StockForecastGrid.cs

@@ -50,12 +50,9 @@ public class StockForecastItem : BaseObject
 
     public List<ProductInstance> ProductInstances { get; set; } = new(0);
 
-    public double Average1 { get; set; }
-    
-    public double Average2 { get; set; }
-    
-    public double Average3 { get; set; }
+    public double Average { get; set; }
     
+    public int MinStockDays { get; set; }
     public double MinStock { get; set; }
     public double GenStock { get; set; }
     public double GenPO { get; set; }
@@ -92,13 +89,15 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
 
     private enum ColumnTag
     {
+        Average,
         MinimumStockRequired,
         GeneralStockHoldings,
         GeneralPurchaseOrders,
         JobStockRequired,
         JobStockHoldings,
         JobPurchaseOrders,
-        BalanceRequired
+        BalanceRequired,
+        Period
     }
     
     private SupplierProduct[]? _supplierProducts = null;
@@ -126,6 +125,10 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
 
     private DynamicGridCustomColumnsComponent<StockForecastItem> ColumnsComponent;
     private string ColumnsTag => "StockForecastGrid";
+
+    public StockForecastProperties? Properties { get; set; }
+
+    public event EventHandler OnPropertiesChanged;
     
     public StockForecastGrid() : base()
     {
@@ -146,6 +149,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
 
         HiddenColumns.Add(x => x.Required);
         HiddenColumns.Add(x => x.Optimised);
+        HiddenColumns.Add(x => x.MinStockDays);
         HiddenColumns.Add(x => x.MinStock);
         HiddenColumns.Add(x => x.GenStock);
         HiddenColumns.Add(x => x.GenPO);
@@ -168,8 +172,14 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
             Position = DynamicActionColumnPosition.Start
         });
         
-        var minStockColumn = CreateColumn(GetMinimumStockLevel, ColumnTag.MinimumStockRequired,"Min.","F2");
-        minStockColumn.ContextMenu = MinimumStock_Menu;
+        var _averageColumn = CreateColumn(GetAverage, ColumnTag.Average,"Avg.","F2");
+        _averageColumn.ContextMenu = Average_Menu;
+
+        var _periodColumn = CreateColumn(GetPeriod, ColumnTag.Period, "Days", "F0");
+        _periodColumn.ContextMenu = MinimumStock_Menu;
+        
+        var _minStockColumn = CreateColumn(GetMinimumStockLevel, ColumnTag.MinimumStockRequired,"Min.","F2");
+        _minStockColumn.ContextMenu = MinimumStock_Menu;
         CreateColumn(GetGeneralStockLevel, ColumnTag.GeneralStockHoldings,"Hld.","F2");
         CreateColumn(GetGeneralPurchaseOrder, ColumnTag.GeneralPurchaseOrders, "PO.","F2");
         CreateColumn(GetBOMBalance, ColumnTag.JobStockRequired, "BOM.","F2");
@@ -253,7 +263,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
 
     private UIComponent? _uicomponent = null;
     private Guid[] _groupIDs = [];
-
+    
     private class UIComponent : DynamicGridGridUIComponent<StockForecastItem>
     {
         private StockForecastGrid Grid;
@@ -282,6 +292,14 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
             if (column is DynamicTextColumn col)
             {
                 var item = Grid.LoadItem(row);
+
+                var periods = (Parent as StockForecastGrid)?.Properties?.AveragePeriods ?? 1;
+                var avg = item.MinStock.IsEffectivelyGreaterThan(0.0) && item.MinStockDays > 0
+                    ? !item.MinStock.IsEffectivelyLessThan(item.Average)
+                        ? Colors.LightGreen.ToBrush(0.5)
+                        : Colors.LightSalmon.ToBrush(0.5)
+                    : Colors.Gainsboro.ToBrush(0.5);
+                    
                 var stock = Math.Max(0.0F, item.MinStock - (item.GenStock + item.GenPO)).IsEffectivelyEqual(0.0F)
                     ? Colors.LightBlue.ToBrush(0.5)
                     : Colors.LightSalmon.ToBrush(0.5);
@@ -296,7 +314,9 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 
                 return col.Tag switch
                 {
-                    ColumnTag.MinimumStockRequired => stock,
+                    ColumnTag.Period => avg,
+                    ColumnTag.Average => avg,
+                    ColumnTag.MinimumStockRequired => avg,
                     ColumnTag.GeneralStockHoldings => stock,
                     ColumnTag.GeneralPurchaseOrders => stock,
                     ColumnTag.JobStockRequired => job,
@@ -342,6 +362,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
         base.ConfigureColumnGroups();
 
         AddColumnGrouping()
+            .AddGroup("Usage", GetColumn(ColumnTag.Average), GetColumn(ColumnTag.Period))
             .AddGroup("General Stock", GetColumn(ColumnTag.MinimumStockRequired), GetColumn(ColumnTag.GeneralPurchaseOrders))
             .AddGroup("Job Stock", GetColumn(ColumnTag.JobStockRequired), GetColumn(ColumnTag.JobPurchaseOrders));
     }
@@ -362,7 +383,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
     {
         var column = new DynamicTextColumn(calculate)
         {
-            Width = 60, 
+            Width = 65, 
             Format=format, 
             Position = DynamicActionColumnPosition.End, 
             Tag = tag,
@@ -377,6 +398,12 @@ 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;
+
+    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;
 
     private object GetGeneralStockLevel(CoreRow? row)
@@ -592,6 +619,31 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 break;
         }
     }
+    
+    private ContextMenu? Average_Menu(CoreRow[]? rows)
+    {
+        if (rows is null || rows.Length > 0 || Properties == null)
+            return null;
+        
+        var menu = new ContextMenu();
+        menu.AddItem("Adjust Usage Calculation", null, AdjustAverageDays_Click);
+        return menu;
+
+    }
+
+    private void AdjustAverageDays_Click()
+    {
+        if (Properties == null)
+            return;
+        var periods = Properties.AveragePeriods;
+        if (NumberEdit.Execute("# Periods", 1, int.MaxValue, ref periods))
+        {
+            Properties.AveragePeriods = periods;
+            OnPropertiesChanged?.Invoke(this, EventArgs.Empty);
+            Refresh(false,true);
+        }
+    }
+    
 
     private ContextMenu? MinimumStock_Menu(CoreRow[]? rows)
     {
@@ -650,6 +702,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 .AddFluent(x => x.Product.ID)
                 .AddFluent(x => x.Style.ID)
                 .AddFluent(x => x.Dimensions.UnitSize)
+                .AddFluent(x => x.MinimumStockDays, caption: "Days Req.", width: 80)
                 .AddFluent(x => x.MinimumStockLevel, caption: "Min. Stock", width: 100);
         }
 
@@ -657,7 +710,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
         {
             editor = base.CustomiseEditor(column, editor);
 
-            if(column.ColumnName == nameof(ProductInstance.MinimumStockLevel))
+            if(column.ColumnName == nameof(ProductInstance.MinimumStockLevel) || column.ColumnName == nameof(ProductInstance.MinimumStockDays))
             {
                 editor.Editable = Editable.Enabled;
             }
@@ -751,6 +804,8 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
     {
         return tag switch
         {
+            ColumnTag.Average => row.Get<StockForecastItem, double>(x => x.Average),
+            ColumnTag.Period => row.Get<StockForecastItem,int>(x => x.MinStockDays),
             ColumnTag.MinimumStockRequired => row.Get<StockForecastItem, double>(x => x.MinStock),
             ColumnTag.GeneralStockHoldings => row.Get<StockForecastItem, double>(x => x.GenStock),
             ColumnTag.GeneralPurchaseOrders => row.Get<StockForecastItem, double>(x => x.GenPO),
@@ -844,6 +899,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
 
         var queries = new IKeyedQueryDef[]
         {
+            
             new KeyedQueryDef<ProductInstance>(
                 new Filter<ProductInstance>(x => x.Product.Group.ID).InList(GroupIDs),
                 //new Filter<ProductInstance>(x=>x.MinimumStockLevel).IsNotEqualTo(0.0).And(x => x.Product.Group.ID).InList(GroupIDs),
@@ -854,9 +910,15 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Data)
                     .Add(x => x.Dimensions.UnitSize)
                     .Add(x => x.Dimensions.Value)
-                    .Add(x => x.MinimumStockLevel)),
+                    .Add(x => x.MinimumStockLevel)
+                    .Add(x => x.MinimumStockDays)
+            ),
+            
             GetQuery<StockHolding>(
-                columns: Columns.None<StockHolding>().Add(x => x.Units)),
+                columns: Columns.None<StockHolding>()
+                    .Add(x => x.Units)
+            ),
+            
             new KeyedQueryDef<PurchaseOrderItem>(
                 new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue)
                     .And(x => x.Product.Group.ID).InList(GroupIDs),
@@ -867,7 +929,9 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     .Add(x => x.Style.ID)
                     .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Data)
                     .Add(x => x.Dimensions.UnitSize)
-                    .Add(x => x.Dimensions.Value)),
+                    .Add(x => x.Dimensions.Value)
+            ),
+            
             new KeyedQueryDef<PurchaseOrderItemAllocation>(
                 new Filter<PurchaseOrderItemAllocation>(x => x.Item.ReceivedDate).IsEqualTo(DateTime.MinValue)
                     .And(x => x.Item.Product.Group.ID).InList(GroupIDs)
@@ -881,19 +945,28 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     .Add(x => x.Item.Style.ID)
                     .AddDimensionsColumns(x => x.Item.Dimensions, Dimensions.ColumnsType.Data)
                     .Add(x => x.Item.Dimensions.UnitSize)
-                    .Add(x => x.Item.Dimensions.Value)),
+                    .Add(x => x.Item.Dimensions.Value)
+            ),
+            
             GetQuery<JobBillOfMaterialsItem>(
                 filter: new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
-                columns: Columns.None<JobBillOfMaterialsItem>().Add(x => x.Quantity)),
+                columns: Columns.None<JobBillOfMaterialsItem>().Add(x => x.Quantity)
+            ),
+            
             GetQuery<StockMovement>(
-                filter: new Filter<StockMovement>(x => x.Type).IsEqualTo(StockMovementType.Issue),
-                columns: Columns.None<StockMovement>().Add(x => x.Units)),
+                filter: new Filter<StockMovement>(x => x.Type).InList(StockMovementType.Issue, StockMovementType.StockTake),
+                columns: Columns.None<StockMovement>()
+                    .Add(x=>x.Date)
+                    .Add(x => x.Units)
+            ),
+            
             GetQuery<SupplierProduct>(
                 columns: Columns.None<SupplierProduct>()
                     .Add(x => x.SupplierLink.ID)
                     .Add(x => x.CostPrice)
                     .Add(x => x.Discount)
-                    .Add(x => x.TaxCode.ID))
+                    .Add(x => x.TaxCode.ID)
+            )
         };
 
         Client.QueryMultiple(
@@ -919,7 +992,8 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     }
                     return item;
                 }
-
+                
+                
                 var productInstances = results.GetArray<ProductInstance>();
                 foreach(var instance in productInstances)
                 {
@@ -928,7 +1002,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     var minStock = DimensionUtils.ConvertDimensions(dimensions, instance.MinimumStockLevel, (f,c) => Client.Query(f,c));
 
                     var item = GetItem(new(instance.Product.ID, instance.Style.ID, dimensions));
-
+                    item.MinStockDays = instance.MinimumStockDays;
                     item.MinStock += minStock;
                     item.ProductInstances.Add(instance);
                 }
@@ -1024,6 +1098,11 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                     
                     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)
                         item.AddJobBOM(movement.Job.ID, movement.Units);
                 }

+ 2 - 0
prs.desktop/Panels/Stock Forecast/StockForecastProperties.cs

@@ -20,6 +20,8 @@ public class StockForecastProperties : IUserConfigurationSettings, IDashboardPro
 
     public DynamicSplitPanelSettings SplitPanelSettings { get; set; } = new();
 
+    public int AveragePeriods { get; set; } = 1;
+
     public bool RequiredOnly { get; set; } = true;
 
     public bool AllStock { get; set; } = true;

+ 9 - 7
prs.server/Forms/ServerGrid.cs

@@ -166,17 +166,19 @@ public class ServerGrid : DynamicGrid<Server>
             null,
             status == DynamicMenuStatus.Enabled && !_consoles.ContainsKey(key)
         );
+        
+        column.AddItem(
+            "Search Log Files",
+            Properties.Resources.service,
+            (row) => SearchLogFiles(row!), 
+            null, 
+            status == DynamicMenuStatus.Enabled
+        );
 
         if (type.Equals(ServerType.Database))
         {
             column.AddSeparator();
-            column.AddItem(
-                "Search Log Files",
-                Properties.Resources.service,
-                (row) => SearchLogFiles(row!), 
-                null, 
-                status == DynamicMenuStatus.Enabled
-            );
+
             column.AddItem(
                 "Custom Fields",
                 Properties.Resources.service,