|
@@ -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);
|
|
|
}
|