|
@@ -146,6 +146,16 @@ public class StockForecastOrderingResult
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+public enum StockForecastOrderingStrategy
|
|
|
+{
|
|
|
+ PerProduct,
|
|
|
+ Exact,
|
|
|
+ RoundUp,
|
|
|
+ LowestUnitPrice,
|
|
|
+ LowestOverallPrice,
|
|
|
+ LowestOverstock
|
|
|
+}
|
|
|
+
|
|
|
public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrderingItem>, ISpecificGrid
|
|
|
{
|
|
|
private List<SupplierProduct> SupplierProducts = [];
|
|
@@ -173,13 +183,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
{
|
|
|
_orderType = value;
|
|
|
|
|
|
- CalculateQuantities();
|
|
|
-
|
|
|
- foreach(var control in QuantityControls)
|
|
|
- {
|
|
|
- control.UpdateControl(OrderType);
|
|
|
- }
|
|
|
-
|
|
|
+ CalculateQuantities(true);
|
|
|
UIComponent.UpdateOrderType(OrderType);
|
|
|
|
|
|
Refresh(true, true);
|
|
@@ -187,6 +191,32 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private StockForecastOrderingStrategy orderStrategy;
|
|
|
+ public StockForecastOrderingStrategy OrderStrategy
|
|
|
+ {
|
|
|
+ get => orderStrategy;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ orderStrategy = value;
|
|
|
+
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.OrderStrategy = value switch
|
|
|
+ {
|
|
|
+ StockForecastOrderingStrategy.Exact => SupplierProductOrderStrategy.Exact,
|
|
|
+ StockForecastOrderingStrategy.LowestOverallPrice => SupplierProductOrderStrategy.LowestOverallPrice,
|
|
|
+ StockForecastOrderingStrategy.LowestUnitPrice => SupplierProductOrderStrategy.LowestUnitPrice,
|
|
|
+ StockForecastOrderingStrategy.LowestOverstock => SupplierProductOrderStrategy.LowestOverstock,
|
|
|
+ StockForecastOrderingStrategy.RoundUp => SupplierProductOrderStrategy.RoundUp,
|
|
|
+ StockForecastOrderingStrategy.PerProduct or _ => item.Product.OrderStrategy
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ CalculateQuantities(false);
|
|
|
+ Refresh(false, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public IEnumerable<StockForecastOrderingResult> Results
|
|
|
{
|
|
|
get
|
|
@@ -316,6 +346,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
.Add(x => x.SupplierLink.ID)
|
|
|
.Add(x => x.Product.ID)
|
|
|
.Add(x => x.Style.ID)
|
|
|
+ .Add(x => x.Style.Code)
|
|
|
.Add(x => x.ForeignCurrencyPrice)
|
|
|
.Add(x => x.CostPrice)
|
|
|
.AddDimensionsColumns(x => x.Dimensions)
|
|
@@ -332,7 +363,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
LoadJobData(OrderData.SelectMany(x => x.GetJobRequiredQuantities().Keys).Distinct().Where(x => x != Guid.Empty));
|
|
|
|
|
|
- CalculateQuantities();
|
|
|
+ CalculateQuantities(true);
|
|
|
|
|
|
_loadedData = true;
|
|
|
}
|
|
@@ -351,42 +382,74 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
return qty;
|
|
|
}
|
|
|
|
|
|
- private void CalculateQuantities()
|
|
|
+ private void CalculateSupplierProduct(StockForecastOrderingItem item)
|
|
|
{
|
|
|
- SetObserving(false);
|
|
|
+ var selectedSupplierProducts = new List<SupplierProduct>();
|
|
|
+ for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
+ {
|
|
|
+ var supplierProduct = SelectSupplierProduct(SupplierProducts.Where(x => x.Product.ID == item.Product.ID && x.Style.ID == item.Style.ID && x.SupplierLink.ID == Suppliers[i].ID), item);
|
|
|
+
|
|
|
+ var qty = item.GetQuantity(i);
|
|
|
+ qty.SupplierProduct = supplierProduct;
|
|
|
+ qty.Total = 0;
|
|
|
+
|
|
|
+ if(supplierProduct is not null)
|
|
|
+ {
|
|
|
+ selectedSupplierProducts.Add(supplierProduct);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- Items.Clear();
|
|
|
- foreach(var dataItem in OrderData)
|
|
|
+ var selectedSupplierProduct = SelectSupplierProduct(selectedSupplierProducts, item);
|
|
|
+ if(selectedSupplierProduct is not null)
|
|
|
{
|
|
|
- if(OrderType == StockForecastOrderingType.StockOrder)
|
|
|
+ var supplierIdx = Suppliers.WithIndex()
|
|
|
+ .FirstOrDefault(x => x.Value.ID == selectedSupplierProduct.SupplierLink.ID, new KeyValuePair<int, SupplierLink>(-1, null)).Key;
|
|
|
+ if(supplierIdx != -1)
|
|
|
{
|
|
|
- var item = new StockForecastOrderingItem();
|
|
|
- item.Product.CopyFrom(dataItem.Product);
|
|
|
- item.Style.CopyFrom(dataItem.Style);
|
|
|
- item.Dimensions.CopyFrom(dataItem.Dimensions);
|
|
|
- item.RequiredQuantity = dataItem.RequiredQuantity;
|
|
|
- item.OrderStrategy = item.Product.OrderStrategy;
|
|
|
- Items.Add(item);
|
|
|
+ item.GetQuantity(supplierIdx).Total = GetRequiredQuantity(item, selectedSupplierProduct);
|
|
|
}
|
|
|
- else
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CalculateQuantities(bool recreateItems)
|
|
|
+ {
|
|
|
+ SetObserving(false);
|
|
|
+
|
|
|
+ if (recreateItems)
|
|
|
+ {
|
|
|
+ Items.Clear();
|
|
|
+ foreach(var dataItem in OrderData)
|
|
|
{
|
|
|
- foreach(var (id, q) in dataItem.GetJobRequiredQuantities())
|
|
|
+ if(OrderType == StockForecastOrderingType.StockOrder)
|
|
|
{
|
|
|
var item = new StockForecastOrderingItem();
|
|
|
item.Product.CopyFrom(dataItem.Product);
|
|
|
item.Style.CopyFrom(dataItem.Style);
|
|
|
item.Dimensions.CopyFrom(dataItem.Dimensions);
|
|
|
- item.Job.ID = id;
|
|
|
-
|
|
|
- if(id != Guid.Empty)
|
|
|
+ item.RequiredQuantity = dataItem.RequiredQuantity;
|
|
|
+ item.OrderStrategy = item.Product.OrderStrategy;
|
|
|
+ Items.Add(item);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ foreach(var (id, q) in dataItem.GetJobRequiredQuantities())
|
|
|
{
|
|
|
- item.Job.CopyFrom(JobDetails[id]);
|
|
|
- }
|
|
|
+ var item = new StockForecastOrderingItem();
|
|
|
+ item.Product.CopyFrom(dataItem.Product);
|
|
|
+ item.Style.CopyFrom(dataItem.Style);
|
|
|
+ item.Dimensions.CopyFrom(dataItem.Dimensions);
|
|
|
+ item.Job.ID = id;
|
|
|
|
|
|
- item.RequiredQuantity = q;
|
|
|
- item.OrderStrategy = item.Product.OrderStrategy;
|
|
|
+ if(id != Guid.Empty)
|
|
|
+ {
|
|
|
+ item.Job.CopyFrom(JobDetails[id]);
|
|
|
+ }
|
|
|
|
|
|
- Items.Add(item);
|
|
|
+ item.RequiredQuantity = q;
|
|
|
+ item.OrderStrategy = item.Product.OrderStrategy;
|
|
|
+
|
|
|
+ Items.Add(item);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -404,31 +467,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
foreach(var item in Items)
|
|
|
{
|
|
|
- var selectedSupplierProducts = new List<SupplierProduct>();
|
|
|
- for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
- {
|
|
|
- var supplierProduct = SelectSupplierProduct(SupplierProducts.Where(x => x.Product.ID == item.Product.ID && x.Style.ID == item.Style.ID && x.SupplierLink.ID == Suppliers[i].ID), item);
|
|
|
-
|
|
|
- var qty = item.GetQuantity(i);
|
|
|
- qty.SupplierProduct = supplierProduct;
|
|
|
- qty.Total = 0;
|
|
|
-
|
|
|
- if(supplierProduct is not null)
|
|
|
- {
|
|
|
- selectedSupplierProducts.Add(supplierProduct);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- var selectedSupplierProduct = SelectSupplierProduct(selectedSupplierProducts, item);
|
|
|
- if(selectedSupplierProduct is not null)
|
|
|
- {
|
|
|
- var supplierIdx = Suppliers.WithIndex()
|
|
|
- .FirstOrDefault(x => x.Value.ID == selectedSupplierProduct.SupplierLink.ID, new KeyValuePair<int, SupplierLink>(-1, null)).Key;
|
|
|
- if(supplierIdx != -1)
|
|
|
- {
|
|
|
- item.GetQuantity(supplierIdx).Total = GetRequiredQuantity(item, selectedSupplierProduct);
|
|
|
- }
|
|
|
- }
|
|
|
+ CalculateSupplierProduct(item);
|
|
|
}
|
|
|
SetObserving(true);
|
|
|
DoChanged();
|
|
@@ -436,12 +475,30 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
private SupplierProduct? SelectSupplierProduct(IEnumerable<SupplierProduct> supplierProducts, StockForecastOrderingItem item)
|
|
|
{
|
|
|
- return null;
|
|
|
+ switch (item.OrderStrategy)
|
|
|
+ {
|
|
|
+ case SupplierProductOrderStrategy.Exact:
|
|
|
+ case SupplierProductOrderStrategy.RoundUp:
|
|
|
+ // First, find the cheapest in the right style and dimensions.
|
|
|
+ return supplierProducts.Where(x => x.Dimensions.Equals(item.Dimensions) && x.Style.ID == item.Style.ID).MinBy(x => x.CostPrice)
|
|
|
+ // Otherwise, find the cheapest in the right dimensions.
|
|
|
+ ?? supplierProducts.Where(x => x.Dimensions.Equals(item.Dimensions)).MinBy(x => x.CostPrice);
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private double GetRequiredQuantity(StockForecastOrderingItem item, SupplierProduct supplierProduct)
|
|
|
{
|
|
|
- return 0;
|
|
|
+ switch (item.OrderStrategy)
|
|
|
+ {
|
|
|
+ case SupplierProductOrderStrategy.Exact:
|
|
|
+ return item.RequiredQuantity;
|
|
|
+ case SupplierProductOrderStrategy.RoundUp:
|
|
|
+ return Math.Ceiling(item.RequiredQuantity);
|
|
|
+ default:
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private bool _loadedColumns = false;
|
|
@@ -472,7 +529,23 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
ActionColumns.Add(new DynamicTemplateColumn(row =>
|
|
|
{
|
|
|
- return null;
|
|
|
+ var item = LoadItem(row);
|
|
|
+
|
|
|
+ var box = new ComboBox();
|
|
|
+ box.ItemsSource = Enum.GetValues<SupplierProductOrderStrategy>()
|
|
|
+ .Select(x => new KeyValuePair<SupplierProductOrderStrategy, string>(x, CoreUtils.Neatify(x.ToString())));
|
|
|
+ box.DisplayMemberPath = "Value";
|
|
|
+ box.SelectedValuePath = "Key";
|
|
|
+ box.SelectedValue = item.OrderStrategy;
|
|
|
+ box.SelectionChanged += (o, e) =>
|
|
|
+ {
|
|
|
+ item.OrderStrategy = (SupplierProductOrderStrategy)box.SelectedValue;
|
|
|
+ CalculateSupplierProduct(item);
|
|
|
+ InvalidateRow(row);
|
|
|
+ };
|
|
|
+ box.Margin = new Thickness(2);
|
|
|
+ box.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ return box;
|
|
|
})
|
|
|
{
|
|
|
HeaderText = "Order Strategy.",
|
|
@@ -589,7 +662,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
{
|
|
|
for(int idx = 0; idx < Suppliers.Length; ++idx)
|
|
|
{
|
|
|
- GetColumnGrouping().AddGroup(Suppliers[idx].Code, QuantityColumns[idx], CostColumns[idx]);
|
|
|
+ GetColumnGrouping().AddGroup(Suppliers[idx].Code, SupplierProductColumns[idx], CostColumns[idx]);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -656,8 +729,6 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
private void InitialiseSupplierColumn(int idx)
|
|
|
{
|
|
|
- var supplierProducts = SupplierProducts.Where(x => x.SupplierLink.ID == Suppliers[idx].ID).ToArray();
|
|
|
-
|
|
|
var contextMenuFunc = (CoreRow[]? rows) =>
|
|
|
{
|
|
|
var row = rows?.FirstOrDefault();
|
|
@@ -678,7 +749,26 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
var qtyColumn = new Tuple<DynamicActionColumn, QuantityControl?>(null!, null);
|
|
|
SupplierProductColumns[idx] = new DynamicTemplateColumn(row =>
|
|
|
{
|
|
|
- return null;
|
|
|
+ var instance = LoadItem(row);
|
|
|
+
|
|
|
+ var comboBox = new ComboBox();
|
|
|
+
|
|
|
+ comboBox.ItemsSource =
|
|
|
+ SupplierProducts.Where(x => x.SupplierLink.ID == Suppliers[idx].ID && x.Product.ID == instance.Product.ID)
|
|
|
+ .Select(x => new KeyValuePair<SupplierProduct?, string>(
|
|
|
+ x,
|
|
|
+ x.Style.ID != Guid.Empty ? $"{x.Dimensions.UnitSize}/{x.Style.Code}" : $"{x.Dimensions.UnitSize}"));
|
|
|
+ comboBox.SelectedValuePath = "Key";
|
|
|
+ comboBox.DisplayMemberPath = "Value";
|
|
|
+
|
|
|
+ var qty = instance.GetQuantity(idx);
|
|
|
+
|
|
|
+ comboBox.Bind(ComboBox.SelectedValueProperty, qty, x => x.SupplierProduct);
|
|
|
+
|
|
|
+ comboBox.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ comboBox.Margin = new Thickness(2);
|
|
|
+
|
|
|
+ return comboBox;
|
|
|
})
|
|
|
{
|
|
|
HeaderText = "Supplier Product.",
|