|
|
@@ -25,6 +25,28 @@ using Columns = InABox.Core.Columns;
|
|
|
|
|
|
namespace PRSDesktop.Panels.StockForecast.OrderScreen;
|
|
|
|
|
|
+public class StockForecastOrderData(ProductLink product, ProductStyleLink style, StockDimensions dimensions)
|
|
|
+{
|
|
|
+ public ProductLink Product { get; set; } = product;
|
|
|
+
|
|
|
+ public ProductStyleLink Style { get; set; } = style;
|
|
|
+
|
|
|
+ public StockDimensions Dimensions { get; set; } = dimensions;
|
|
|
+
|
|
|
+ public double RequiredQuantity { get; set; }
|
|
|
+
|
|
|
+ private Dictionary<Guid, double> JobRequiredQuantities { get; set; } = [];
|
|
|
+
|
|
|
+ public Dictionary<Guid, double> GetJobRequiredQuantities()
|
|
|
+ {
|
|
|
+ return JobRequiredQuantities;
|
|
|
+ }
|
|
|
+ public void SetJobRequiredQuantity(Guid jobID, double requiredQty)
|
|
|
+ {
|
|
|
+ JobRequiredQuantities[jobID] = requiredQty;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
public enum StockForecastOrderingType
|
|
|
{
|
|
|
StockOrder,
|
|
|
@@ -35,29 +57,35 @@ public class StockForecastOrderingItemQuantity
|
|
|
{
|
|
|
public event Action? Changed;
|
|
|
|
|
|
- private double _stockTotal;
|
|
|
- public double StockTotal
|
|
|
+ private double _total;
|
|
|
+ public double Total
|
|
|
{
|
|
|
- get => _stockTotal;
|
|
|
+ get => _total;
|
|
|
set
|
|
|
{
|
|
|
- _stockTotal = value;
|
|
|
+ _total = value;
|
|
|
Changed?.Invoke();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public Dictionary<Guid, double> JobTotals { get; init; } = [];
|
|
|
+ private SupplierProduct? _supplierProduct;
|
|
|
+ /// <summary>
|
|
|
+ /// Indicates the Supplier Product that has been selected for this cell. This comes from the combobox column.
|
|
|
+ /// </summary>
|
|
|
+ public SupplierProduct? SupplierProduct
|
|
|
+ {
|
|
|
+ get => _supplierProduct;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _supplierProduct = value;
|
|
|
+ Changed?.Invoke();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
public void DoChanged()
|
|
|
{
|
|
|
Changed?.Invoke();
|
|
|
}
|
|
|
-
|
|
|
- public double JobTotal => JobTotals.Sum(x => x.Value);
|
|
|
-
|
|
|
- public double GetTotal(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
|
|
|
- ? StockTotal
|
|
|
- : JobTotal;
|
|
|
}
|
|
|
|
|
|
public class StockForecastOrderingItem : BaseObject
|
|
|
@@ -72,27 +100,25 @@ public class StockForecastOrderingItem : BaseObject
|
|
|
public StockDimensions Dimensions { get; set; }
|
|
|
|
|
|
[EditorSequence(4)]
|
|
|
+ public JobLink Job { get; set; }
|
|
|
+
|
|
|
+ [EditorSequence(5)]
|
|
|
[DoubleEditor]
|
|
|
public double RequiredQuantity { get; set; }
|
|
|
|
|
|
- private Dictionary<Guid, double> JobRequiredQuantities { get; set; } = [];
|
|
|
+ [EditorSequence(6)]
|
|
|
+ [EnumLookupEditor(typeof(SupplierProductOrderStrategy))]
|
|
|
+ public SupplierProductOrderStrategy OrderStrategy { get; set; }
|
|
|
|
|
|
- public Dictionary<Guid, double> GetJobRequiredQuantities()
|
|
|
- {
|
|
|
- return JobRequiredQuantities;
|
|
|
- }
|
|
|
- public void SetJobRequiredQuantity(Guid jobID, double requiredQty)
|
|
|
- {
|
|
|
- JobRequiredQuantities[jobID] = requiredQty;
|
|
|
- }
|
|
|
+ public bool CustomStrategy { get; set; } = false;
|
|
|
|
|
|
- private StockForecastOrderingItemQuantity[] Quantities = [];
|
|
|
+ public StockForecastOrderingItemQuantity[] Quantities = [];
|
|
|
|
|
|
public StockForecastOrderingItemQuantity GetQuantity(int i) => Quantities[i];
|
|
|
|
|
|
public double GetTotalQuantity(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
|
|
|
- ? Quantities.Sum(x => x.StockTotal)
|
|
|
- : Quantities.Sum(x => x.JobTotal);
|
|
|
+ ? Quantities.Sum(x => x.Total)
|
|
|
+ : Quantities.Sum(x => x.Total);
|
|
|
|
|
|
public void SetQuantities(StockForecastOrderingItemQuantity[] quantities)
|
|
|
{
|
|
|
@@ -122,13 +148,26 @@ public class StockForecastOrderingResult
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+public enum StockForecastOrderingStrategy
|
|
|
+{
|
|
|
+ PerProduct,
|
|
|
+ Exact,
|
|
|
+ RoundUp,
|
|
|
+ LowestUnitPrice,
|
|
|
+ LowestOverallPrice,
|
|
|
+ LowestOverstock
|
|
|
+}
|
|
|
+
|
|
|
public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrderingItem>, ISpecificGrid
|
|
|
{
|
|
|
private List<SupplierProduct> SupplierProducts = [];
|
|
|
private SupplierLink[] Suppliers = [];
|
|
|
|
|
|
+ public IList<StockForecastOrderData> OrderData { get; set; }
|
|
|
+
|
|
|
public double TotalQuantity => Items.Sum(x => x.GetTotalQuantity(OrderType));
|
|
|
|
|
|
+ private DynamicActionColumn[] SupplierProductColumns = [];
|
|
|
private DynamicActionColumn[] QuantityColumns = [];
|
|
|
private DynamicActionColumn[] CostColumns = [];
|
|
|
|
|
|
@@ -146,13 +185,38 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
{
|
|
|
_orderType = value;
|
|
|
|
|
|
- CalculateQuantities();
|
|
|
+ CalculateQuantities(true);
|
|
|
+ UIComponent.UpdateOrderType(OrderType);
|
|
|
+
|
|
|
+ Refresh(true, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private StockForecastOrderingStrategy orderStrategy;
|
|
|
+ public StockForecastOrderingStrategy OrderStrategy
|
|
|
+ {
|
|
|
+ get => orderStrategy;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ orderStrategy = value;
|
|
|
|
|
|
- foreach(var control in QuantityControls)
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.OrderStrategy = value switch
|
|
|
{
|
|
|
- control.UpdateControl(OrderType);
|
|
|
- }
|
|
|
+ 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
|
|
|
+ };
|
|
|
+ item.CustomStrategy = false;
|
|
|
}
|
|
|
+
|
|
|
+ CalculateQuantities(false);
|
|
|
+ Refresh(false, true);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -166,25 +230,20 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
foreach(var item in Items)
|
|
|
{
|
|
|
var qty = item.GetQuantity(i);
|
|
|
- var supplierProduct = GetSupplierProduct(item, supplier.ID);
|
|
|
- if (supplierProduct is null)
|
|
|
+ if (qty.SupplierProduct is null)
|
|
|
{
|
|
|
- // If this is true, then the quantities also will have to be true.
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- if(OrderType == StockForecastOrderingType.StockOrder && qty.StockTotal > 0)
|
|
|
+ if(qty.Total > 0)
|
|
|
{
|
|
|
- yield return new(supplier, null, item, qty.StockTotal, supplierProduct);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- foreach(var (jobID, q) in qty.JobTotals)
|
|
|
+ if(OrderType == StockForecastOrderingType.StockOrder && qty.Total > 0)
|
|
|
+ {
|
|
|
+ yield return new(supplier, null, item, qty.Total, qty.SupplierProduct);
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
- if(q > 0)
|
|
|
- {
|
|
|
- yield return new(supplier, new() { ID = jobID }, item, q, supplierProduct);
|
|
|
- }
|
|
|
+ yield return new(supplier, item.Job, item, qty.Total, qty.SupplierProduct);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -221,7 +280,12 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
Parent = grid;
|
|
|
Grid = grid;
|
|
|
|
|
|
- DataGrid.FrozenColumnCount = 7;
|
|
|
+ UpdateOrderType(grid.OrderType);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void UpdateOrderType(StockForecastOrderingType type)
|
|
|
+ {
|
|
|
+ DataGrid.FrozenColumnCount = type == StockForecastOrderingType.StockOrder ? 8 : 9;
|
|
|
}
|
|
|
|
|
|
protected override Brush? GetCellSelectionBackgroundBrush()
|
|
|
@@ -238,7 +302,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
var idx = Math.Max(qIdx, Grid.CostColumns.IndexOf(ac));
|
|
|
if(idx != -1)
|
|
|
{
|
|
|
- var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[idx].ID);
|
|
|
+ var supplierProduct = item.GetQuantity(idx).SupplierProduct;
|
|
|
if(supplierProduct is null)
|
|
|
{
|
|
|
return new SolidColorBrush(Colors.Gainsboro);
|
|
|
@@ -285,13 +349,14 @@ 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)
|
|
|
.Add(x => x.SupplierLink.Code);
|
|
|
|
|
|
SupplierProducts = Client.Query(
|
|
|
- new Filter<SupplierProduct>(x => x.Product.ID).InList(Items.Select(x => x.Product.ID).ToArray())
|
|
|
+ new Filter<SupplierProduct>(x => x.Product.ID).InList(OrderData.Select(x => x.Product.ID).ToArray())
|
|
|
.And(x => x.SupplierLink.ID).IsNotEqualTo(Guid.Empty),
|
|
|
supplierColumns,
|
|
|
new SortOrder<SupplierProduct>(x => x.SupplierLink.Code))
|
|
|
@@ -299,18 +364,9 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).ToArray();
|
|
|
|
|
|
- foreach(var (itemIdx, item) in Items.WithIndex())
|
|
|
- {
|
|
|
- var quantities = new StockForecastOrderingItemQuantity[Suppliers.Length];
|
|
|
- for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
- {
|
|
|
- quantities[i] = CreateQuantity(itemIdx);
|
|
|
- }
|
|
|
-
|
|
|
- item.SetQuantities(quantities);
|
|
|
- }
|
|
|
+ LoadJobData(OrderData.SelectMany(x => x.GetJobRequiredQuantities().Keys).Distinct().Where(x => x != Guid.Empty));
|
|
|
|
|
|
- CalculateQuantities();
|
|
|
+ CalculateQuantities(true);
|
|
|
|
|
|
_loadedData = true;
|
|
|
}
|
|
|
@@ -320,6 +376,8 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
var qty = new StockForecastOrderingItemQuantity();
|
|
|
qty.Changed += () =>
|
|
|
{
|
|
|
+ if (!_observing) return;
|
|
|
+
|
|
|
var row = Data.Rows[itemIdx];
|
|
|
InvalidateRow(row);
|
|
|
DoChanged();
|
|
|
@@ -327,82 +385,197 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
return qty;
|
|
|
}
|
|
|
|
|
|
- private void CalculateQuantities()
|
|
|
+ private SupplierProduct CalculateSupplierProduct(StockForecastOrderingItem item, int supplierIdx)
|
|
|
{
|
|
|
- SetObserving(false);
|
|
|
- foreach(var item in Items)
|
|
|
+ var supplierProduct = SelectSupplierProduct(SupplierProducts.Where(x => x.Product.ID == item.Product.ID && x.Style.ID == item.Style.ID && x.SupplierLink.ID == Suppliers[supplierIdx].ID), item);
|
|
|
+
|
|
|
+ var qty = item.GetQuantity(supplierIdx);
|
|
|
+ qty.SupplierProduct = supplierProduct;
|
|
|
+ qty.Total = 0;
|
|
|
+ return supplierProduct;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CalculateSupplierProduct(StockForecastOrderingItem item)
|
|
|
+ {
|
|
|
+ var selectedSupplierProducts = new List<SupplierProduct>();
|
|
|
+ for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
{
|
|
|
- var supplierProduct = GetSupplierProduct(item);
|
|
|
- for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
+ var supplierProduct = CalculateSupplierProduct(item, i);
|
|
|
+ if(supplierProduct is not null)
|
|
|
{
|
|
|
- var qty = item.GetQuantity(i);
|
|
|
+ selectedSupplierProducts.Add(supplierProduct);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- var supplier = Suppliers[i];
|
|
|
- if(supplierProduct is not null && supplier.ID == supplierProduct.SupplierLink.ID)
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void CalculateQuantities(bool recreateItems)
|
|
|
+ {
|
|
|
+ SetObserving(false);
|
|
|
+
|
|
|
+ if (recreateItems)
|
|
|
+ {
|
|
|
+ Items.Clear();
|
|
|
+ foreach(var dataItem in OrderData)
|
|
|
+ {
|
|
|
+ if(OrderType == StockForecastOrderingType.StockOrder)
|
|
|
{
|
|
|
- if(OrderType == StockForecastOrderingType.StockOrder)
|
|
|
- {
|
|
|
- qty.StockTotal = qty.JobTotal;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- qty.JobTotals.Clear();
|
|
|
- foreach(var (id, q) in item.GetJobRequiredQuantities())
|
|
|
- {
|
|
|
- qty.JobTotals[id] = q;
|
|
|
- }
|
|
|
- }
|
|
|
+ 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);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- if(OrderType == StockForecastOrderingType.StockOrder)
|
|
|
- {
|
|
|
- qty.StockTotal = 0;
|
|
|
- }
|
|
|
- else
|
|
|
+ foreach(var (id, q) in dataItem.GetJobRequiredQuantities())
|
|
|
{
|
|
|
- foreach(var id in item.GetJobRequiredQuantities().Keys)
|
|
|
+ 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)
|
|
|
{
|
|
|
- qty.JobTotals[id] = 0;
|
|
|
+ item.Job.CopyFrom(JobDetails[id]);
|
|
|
}
|
|
|
+
|
|
|
+ item.RequiredQuantity = q;
|
|
|
+ item.OrderStrategy = item.Product.OrderStrategy;
|
|
|
+
|
|
|
+ Items.Add(item);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ foreach(var (itemIdx, item) in Items.WithIndex())
|
|
|
+ {
|
|
|
+ var quantities = new StockForecastOrderingItemQuantity[Suppliers.Length];
|
|
|
+ for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
+ {
|
|
|
+ quantities[i] = CreateQuantity(itemIdx);
|
|
|
+ }
|
|
|
+
|
|
|
+ item.SetQuantities(quantities);
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ CalculateSupplierProduct(item);
|
|
|
+ }
|
|
|
SetObserving(true);
|
|
|
DoChanged();
|
|
|
+ }
|
|
|
|
|
|
- InvalidateGrid();
|
|
|
+ private SupplierProduct? SelectSupplierProduct(IEnumerable<SupplierProduct> supplierProducts, StockForecastOrderingItem item)
|
|
|
+ {
|
|
|
+ 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)
|
|
|
+ {
|
|
|
+ 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;
|
|
|
protected override DynamicGridColumns LoadColumns()
|
|
|
{
|
|
|
if (!_loadedData)
|
|
|
{
|
|
|
LoadData();
|
|
|
}
|
|
|
- ActionColumns.Clear();
|
|
|
-
|
|
|
- ActionColumns.Add(new DynamicImageColumn(Warning_Image) { Position = DynamicActionColumnPosition.Start });
|
|
|
- ActionColumns.Add(new DynamicImagePreviewColumn<StockForecastOrderingItem>(x => x.Product.Image) { Position = DynamicActionColumnPosition.Start });
|
|
|
|
|
|
var columns = new DynamicGridColumns();
|
|
|
columns.Add<StockForecastOrderingItem, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
|
|
|
columns.Add<StockForecastOrderingItem, string>(x => x.Product.Name, 200, "Product Name", "", Alignment.MiddleLeft);
|
|
|
- columns.Add<StockForecastOrderingItem, string>(x => x.Style.Code, 80, "Style", "", Alignment.MiddleCenter);
|
|
|
columns.Add<StockForecastOrderingItem, string>(x => x.Dimensions.UnitSize, 80, "Size", "", Alignment.MiddleCenter);
|
|
|
+ columns.Add<StockForecastOrderingItem, string>(x => x.Style.Code, 80, "Style", "", Alignment.MiddleCenter);
|
|
|
+ if(OrderType == StockForecastOrderingType.JobOrder)
|
|
|
+ {
|
|
|
+ columns.Add<StockForecastOrderingItem, string>(x => x.Job.JobNumber, 80, "Job No.", "", Alignment.MiddleCenter);
|
|
|
+ }
|
|
|
columns.Add<StockForecastOrderingItem, double>(x => x.RequiredQuantity, 80, "Required", "", Alignment.MiddleCenter);
|
|
|
|
|
|
- QuantityColumns = new DynamicActionColumn[Suppliers.Length];
|
|
|
- CostColumns = new DynamicActionColumn[Suppliers.Length];
|
|
|
- QuantityControls.Clear();
|
|
|
-
|
|
|
- for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
+ if (!_loadedColumns)
|
|
|
{
|
|
|
- InitialiseSupplierColumn(i);
|
|
|
- }
|
|
|
+ ActionColumns.Clear();
|
|
|
|
|
|
- ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
|
|
|
+ ActionColumns.Add(new DynamicImageColumn(Warning_Image) { Position = DynamicActionColumnPosition.Start });
|
|
|
+ ActionColumns.Add(new DynamicImagePreviewColumn<StockForecastOrderingItem>(x => x.Product.Image) { Position = DynamicActionColumnPosition.Start });
|
|
|
+
|
|
|
+ ActionColumns.Add(new DynamicTemplateColumn(row =>
|
|
|
+ {
|
|
|
+ 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.CustomStrategy ? null : item.OrderStrategy;
|
|
|
+ box.SelectionChanged += (o, e) =>
|
|
|
+ {
|
|
|
+ if (box.SelectedValue is not SupplierProductOrderStrategy strategy) return;
|
|
|
+ item.OrderStrategy = strategy;
|
|
|
+ item.CustomStrategy = false;
|
|
|
+ CalculateSupplierProduct(item);
|
|
|
+ InvalidateRow(row);
|
|
|
+ };
|
|
|
+ box.Margin = new Thickness(2);
|
|
|
+ box.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ return box;
|
|
|
+ })
|
|
|
+ {
|
|
|
+ HeaderText = "Order Strategy.",
|
|
|
+ Width = 120
|
|
|
+ });
|
|
|
+
|
|
|
+ SupplierProductColumns = new DynamicActionColumn[Suppliers.Length];
|
|
|
+ QuantityColumns = new DynamicActionColumn[Suppliers.Length];
|
|
|
+ CostColumns = new DynamicActionColumn[Suppliers.Length];
|
|
|
+ QuantityControls.Clear();
|
|
|
+
|
|
|
+ for(int i = 0; i < Suppliers.Length; ++i)
|
|
|
+ {
|
|
|
+ InitialiseSupplierColumn(i);
|
|
|
+ }
|
|
|
+
|
|
|
+ ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
|
|
|
+
|
|
|
+ _loadedColumns = true;
|
|
|
+ }
|
|
|
|
|
|
return columns;
|
|
|
}
|
|
|
@@ -461,8 +634,6 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
foreach (var (itemIdx, item) in Items.WithIndex())
|
|
|
{
|
|
|
- var populateSupplierProduct = GetSupplierProduct(item);
|
|
|
-
|
|
|
var quantities = new StockForecastOrderingItemQuantity[newSuppliers.Length];
|
|
|
for (int i = 0; i < Suppliers.Length; ++i)
|
|
|
{
|
|
|
@@ -471,17 +642,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
var newQty = CreateQuantity(itemIdx);
|
|
|
|
|
|
quantities[newIdx] = newQty;
|
|
|
- if (OrderType == StockForecastOrderingType.StockOrder)
|
|
|
- {
|
|
|
- newQty.StockTotal = 0;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- foreach (var id in item.GetJobRequiredQuantities().Keys)
|
|
|
- {
|
|
|
- newQty.JobTotals[id] = 0;
|
|
|
- }
|
|
|
- }
|
|
|
+ newQty.Total = 0;
|
|
|
item.SetQuantities(quantities);
|
|
|
}
|
|
|
|
|
|
@@ -511,7 +672,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]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -549,102 +710,28 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
|
|
|
public void UpdateControl(StockForecastOrderingType mode)
|
|
|
{
|
|
|
- var supplierProduct = Parent.GetSupplierProduct(Item, Parent.Suppliers[SupplierIndex].ID);
|
|
|
- if(supplierProduct is null)
|
|
|
+ // If no supplier product has been selected for this cell, we can't allow the user to select a quantity.
|
|
|
+ if(Item.GetQuantity(SupplierIndex).SupplierProduct is null)
|
|
|
{
|
|
|
Content = null;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- if(mode == StockForecastOrderingType.StockOrder)
|
|
|
+ // Otherwise, simple quantity textbox editor.
|
|
|
+ var editor = new DoubleTextBox
|
|
|
{
|
|
|
- var editor = new DoubleTextBox
|
|
|
- {
|
|
|
- VerticalAlignment = VerticalAlignment.Stretch,
|
|
|
- HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
|
- Background = new SolidColorBrush(Colors.LightYellow),
|
|
|
- BorderThickness = new Thickness(0.0),
|
|
|
- MinValue = 0.0,
|
|
|
- Value = Item.GetQuantity(SupplierIndex).StockTotal
|
|
|
- };
|
|
|
- editor.ValueChanged += (o, e) =>
|
|
|
- {
|
|
|
- Item.GetQuantity(SupplierIndex).StockTotal = editor.Value ?? default;
|
|
|
- };
|
|
|
- Content = editor;
|
|
|
- }
|
|
|
- else if(mode == StockForecastOrderingType.JobOrder)
|
|
|
+ VerticalAlignment = VerticalAlignment.Stretch,
|
|
|
+ HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
|
+ Background = new SolidColorBrush(Colors.LightYellow),
|
|
|
+ BorderThickness = new Thickness(0.0),
|
|
|
+ MinValue = 0.0,
|
|
|
+ Value = Item.GetQuantity(SupplierIndex).Total
|
|
|
+ };
|
|
|
+ editor.ValueChanged += (o, e) =>
|
|
|
{
|
|
|
- var grid = new Grid();
|
|
|
- grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
|
- grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
|
|
|
-
|
|
|
- var editor = new TextBox
|
|
|
- {
|
|
|
- VerticalAlignment = VerticalAlignment.Stretch,
|
|
|
- HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
|
- VerticalContentAlignment = VerticalAlignment.Center,
|
|
|
- HorizontalContentAlignment = HorizontalAlignment.Center,
|
|
|
- Background = new SolidColorBrush(Colors.White),
|
|
|
- BorderThickness = new Thickness(0.0),
|
|
|
- IsReadOnly = true,
|
|
|
- Text = string.Format("{0:F2}", Item.GetQuantity(SupplierIndex).JobTotal)
|
|
|
- };
|
|
|
- Grid.SetColumn(editor, 0);
|
|
|
- grid.Children.Add(editor);
|
|
|
-
|
|
|
- var btn = new Button
|
|
|
- {
|
|
|
- VerticalAlignment = VerticalAlignment.Stretch,
|
|
|
- VerticalContentAlignment = VerticalAlignment.Center,
|
|
|
- HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
|
- Content = "..",
|
|
|
- Margin = new Thickness(1),
|
|
|
- Focusable = false
|
|
|
- };
|
|
|
- btn.SetValue(Grid.ColumnProperty, 1);
|
|
|
- btn.SetValue(Grid.RowProperty, 0);
|
|
|
- btn.Click += (o, e) =>
|
|
|
- {
|
|
|
- var qty = Item.GetQuantity(SupplierIndex);
|
|
|
-
|
|
|
- Parent.LoadJobData(qty.JobTotals.Keys);
|
|
|
-
|
|
|
- var items = qty.JobTotals.Select(x =>
|
|
|
- {
|
|
|
- var item = new StockForecastOrderingJobItem
|
|
|
- {
|
|
|
- JobID = x.Key,
|
|
|
- RequiredQuantity = Item.GetJobRequiredQuantities().GetValueOrDefault(x.Key),
|
|
|
- Quantity = x.Value
|
|
|
- };
|
|
|
- if(item.JobID == Guid.Empty)
|
|
|
- {
|
|
|
- item.Job = "General Stock";
|
|
|
- }
|
|
|
- else if(Parent.JobDetails.TryGetValue(item.JobID, out var job))
|
|
|
- {
|
|
|
- item.Job = $"{job.JobNumber}: {job.Name}";
|
|
|
- }
|
|
|
- return item;
|
|
|
- }).ToList();
|
|
|
-
|
|
|
- var window = new StockForecastOrderJobScreen();
|
|
|
- window.Items = items;
|
|
|
- if(window.ShowDialog() == true)
|
|
|
- {
|
|
|
- foreach(var item in items)
|
|
|
- {
|
|
|
- qty.JobTotals[item.JobID] = item.Quantity;
|
|
|
- }
|
|
|
- qty.DoChanged();
|
|
|
- editor.Text = string.Format("{0:F2}", Item.GetQuantity(SupplierIndex).JobTotal);
|
|
|
- }
|
|
|
- };
|
|
|
- grid.Children.Add(btn);
|
|
|
-
|
|
|
- Content = grid;
|
|
|
- }
|
|
|
+ Item.GetQuantity(SupplierIndex).Total = editor.Value ?? default;
|
|
|
+ };
|
|
|
+ Content = editor;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -652,27 +739,57 @@ 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();
|
|
|
if (row is null) return null;
|
|
|
|
|
|
var item = LoadItem(row);
|
|
|
- var supplierProduct = GetSupplierProduct(item, Suppliers[idx].ID);
|
|
|
- if (supplierProduct is not null)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
|
|
|
var menu = new ContextMenu();
|
|
|
menu.AddItem("Create Supplier Product", null, new Tuple<StockForecastOrderingItem, int>(item, idx), CreateSupplierProduct_Click);
|
|
|
return menu;
|
|
|
};
|
|
|
|
|
|
- // Making local copy of index so that the lambda can use it, and not the changed value of 'i'.
|
|
|
var qtyColumn = new Tuple<DynamicActionColumn, QuantityControl?>(null!, null);
|
|
|
+ SupplierProductColumns[idx] = new DynamicTemplateColumn(row =>
|
|
|
+ {
|
|
|
+ var instance = LoadItem(row);
|
|
|
+
|
|
|
+ var comboBox = new ComboBox();
|
|
|
+
|
|
|
+ var items = 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}"))
|
|
|
+ .ToArray();
|
|
|
+ comboBox.SelectedValuePath = "Key";
|
|
|
+
|
|
|
+ comboBox.ItemsSource = items;
|
|
|
+ comboBox.DisplayMemberPath = "Value";
|
|
|
+
|
|
|
+ var qty = instance.GetQuantity(idx);
|
|
|
+
|
|
|
+ comboBox.Bind(ComboBox.SelectedValueProperty, qty, x => x.SupplierProduct);
|
|
|
+ comboBox.SelectionChanged += (o, e) =>
|
|
|
+ {
|
|
|
+ instance.CustomStrategy = true;
|
|
|
+ InvalidateRow(row);
|
|
|
+ };
|
|
|
+
|
|
|
+ comboBox.VerticalContentAlignment = VerticalAlignment.Center;
|
|
|
+ comboBox.Margin = new Thickness(2);
|
|
|
+ if(items.Length == 0)
|
|
|
+ {
|
|
|
+ comboBox.IsEnabled = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return comboBox;
|
|
|
+ })
|
|
|
+ {
|
|
|
+ HeaderText = "Supplier Product.",
|
|
|
+ Width = 80
|
|
|
+ };
|
|
|
QuantityColumns[idx] = new DynamicTemplateColumn(row =>
|
|
|
{
|
|
|
var instance = LoadItem(row);
|
|
|
@@ -693,13 +810,10 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
}
|
|
|
|
|
|
var instance = LoadItem(row);
|
|
|
- var qty = OrderType == StockForecastOrderingType.StockOrder
|
|
|
- ? instance.GetQuantity(idx).StockTotal
|
|
|
- : instance.GetQuantity(idx).JobTotal;
|
|
|
- var supplierProduct = GetSupplierProduct(instance, Suppliers[idx].ID);
|
|
|
- if(supplierProduct is not null)
|
|
|
+ var qty = instance.GetQuantity(idx);//.Total;
|
|
|
+ if(qty.SupplierProduct is not null)
|
|
|
{
|
|
|
- return $"{qty * supplierProduct.CostPrice:C2}";
|
|
|
+ return $"{qty.Total * qty.SupplierProduct.CostPrice:C2}";
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
@@ -721,6 +835,7 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
return summary;
|
|
|
}
|
|
|
};
|
|
|
+ ActionColumns.Add(SupplierProductColumns[idx]);
|
|
|
ActionColumns.Add(QuantityColumns[idx]);
|
|
|
ActionColumns.Add(CostColumns[idx]);
|
|
|
}
|
|
|
@@ -738,48 +853,15 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
|
|
|
{
|
|
|
SupplierProducts.Add(supplierProduct);
|
|
|
+ var qty = item.GetQuantity(supplierIdx);
|
|
|
+ if(qty.SupplierProduct is null)
|
|
|
+ {
|
|
|
+ CalculateSupplierProduct(item, supplierIdx);
|
|
|
+ }
|
|
|
InvalidateGrid();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static bool Matches(StockForecastOrderingItem item, SupplierProduct supplierProduct)
|
|
|
- {
|
|
|
- return item.Product.ID == supplierProduct.Product.ID
|
|
|
- && item.Style.ID == supplierProduct.Style.ID
|
|
|
- && item.Dimensions.Equals(supplierProduct.Dimensions);
|
|
|
- }
|
|
|
- private static bool Matches(ProductInstance instance, SupplierProduct supplierProduct)
|
|
|
- {
|
|
|
- return instance.Product.ID == supplierProduct.Product.ID
|
|
|
- && instance.Style.ID == supplierProduct.Style.ID
|
|
|
- && instance.Dimensions.Equals(supplierProduct.Dimensions);
|
|
|
- }
|
|
|
-
|
|
|
- private SupplierProduct? GetSupplierProduct(StockForecastOrderingItem item)
|
|
|
- {
|
|
|
- var defaultSupplierProduct = SupplierProducts.FirstOrDefault(x => x.ID == item.Product.Supplier.ID);
|
|
|
- if(defaultSupplierProduct is not null && Matches(item, defaultSupplierProduct))
|
|
|
- {
|
|
|
- return defaultSupplierProduct;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- return SupplierProducts.FirstOrDefault(x => Matches(item, x));
|
|
|
- }
|
|
|
- }
|
|
|
- private SupplierProduct? GetSupplierProduct(ProductInstance instance, Guid supplierID)
|
|
|
- {
|
|
|
- return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(instance, x));
|
|
|
- }
|
|
|
- private SupplierProduct? GetSupplierProduct(StockForecastOrderingItem item, Guid supplierID)
|
|
|
- {
|
|
|
- return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(item, x));
|
|
|
- }
|
|
|
- //private double GetQuantity(SupplierProduct product)
|
|
|
- //{
|
|
|
- // var instance = ProductInstances.WithIndex().Where(x => x.Value.Product.ID == product.ID)
|
|
|
- //}
|
|
|
-
|
|
|
private class CostAggregate : ISummaryAggregate
|
|
|
{
|
|
|
public double Sum { get; private set; }
|
|
|
@@ -808,11 +890,11 @@ public class StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrder
|
|
|
{
|
|
|
var rowIdx = dataRow.Row.Table.Rows.IndexOf(dataRow.Row);
|
|
|
var item = Grid.LoadItem(Grid.Data.Rows[rowIdx]);
|
|
|
- var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[SupplierIndex].ID);
|
|
|
- if(supplierProduct is not null)
|
|
|
+
|
|
|
+ var qty = item.GetQuantity(SupplierIndex);
|
|
|
+ if(qty.SupplierProduct is not null)
|
|
|
{
|
|
|
- var qty = item.GetQuantity(SupplierIndex);
|
|
|
- Sum += qty.GetTotal(Grid.OrderType) * supplierProduct.CostPrice;
|
|
|
+ Sum += qty.Total * qty.SupplierProduct.CostPrice;
|
|
|
}
|
|
|
}
|
|
|
}
|