1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141 |
- using Comal.Classes;
- using InABox.Clients;
- using InABox.Core;
- using InABox.DynamicGrid;
- using InABox.Wpf;
- using InABox.WPF;
- using NPOI.SS.Formula.Functions;
- using Syncfusion.Data;
- using Syncfusion.Data.Extensions;
- using Syncfusion.UI.Xaml.Grid;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using Columns = InABox.Core.Columns;
- using PRSDesktop.Panels.StockForecast.OrderScreen;
- using PRSDimensionUtils;
- using Syncfusion.Windows.Shared;
- namespace PRSDesktop;
- public class StockForecastBreakupKey(Guid jobID, Guid requiID)
- {
- public Guid JobID { get; set; } = jobID;
- public Guid RequiID { get; set; } = requiID;
- public override bool Equals(object? obj)
- {
- return obj is StockForecastBreakupKey key && key.JobID == JobID && key.RequiID == RequiID;
- }
- public override int GetHashCode()
- {
- return HashCode.Combine(JobID, RequiID);
- }
- }
- 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; }
- public class QuantityBreakup(Guid jobID, Guid requiID, string description, double qty)
- {
- // This may be blank, in which case this refers to General Stock.
- public Guid JobID { get; set; } = jobID;
- public Guid JobRequiItemID { get; set; } = requiID;
- public string Description { get; set; } = description;
- public double Quantity { get; set; } = qty;
- }
-
- private List<QuantityBreakup> RequiredQuantities { get; set; } = [];
- public List<QuantityBreakup> GetRequiredQuantities() => RequiredQuantities;
-
- public void SetRequiredQuantity(Guid jobID, Guid requiID, string description, double qty)
- {
- RequiredQuantities.Add(new(jobID, requiID, description,qty));
- }
- }
- public enum StockForecastOrderingType
- {
- StockOrder,
- Breakup
- }
- public class StockForecastOrderingItemQuantity
- {
- public event Action? Changed;
- private double orderTotal;
- public double OrderTotal
- {
- get => orderTotal;
- set
- {
- orderTotal = value;
- Changed?.Invoke();
- }
- }
- public Dictionary<StockForecastBreakupKey, double> Breakups { 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 => Breakups.Values.Sum();
- public double GetTotal(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
- ? OrderTotal
- : JobTotal;
- }
- public class StockOrderingItem : BaseObject
- {
- [EditorSequence(1)]
- public ProductLink Product { get; set; }
- [EditorSequence(2)]
- public ProductStyleLink Style { get; set; }
- [EditorSequence(3)]
- public StockDimensions Dimensions { get; set; }
-
- [EditorSequence(5)]
- [DoubleEditor]
- public double RequiredQuantity { get; set; }
- [EditorSequence(6)]
- [EnumLookupEditor(typeof(SupplierProductOrderStrategy))]
- public SupplierProductOrderStrategy OrderStrategy { get; set; }
- private Dictionary<StockForecastBreakupKey, double> JobRequiredQuantities { get; set; } = new()
- {
- { new(Guid.Empty, Guid.Empty), 0.0 }
- };
- public Dictionary<StockForecastBreakupKey, double> GetJobRequiredQuantities()
- {
- return JobRequiredQuantities;
- }
-
- public void SetJobRequiredQuantity(Guid jobID, Guid requiID, double requiredQuantity)
- {
- JobRequiredQuantities[new(jobID, requiID)] = requiredQuantity;
- }
- public bool CustomStrategy { get; set; } = false;
- public StockForecastOrderingItemQuantity[] Quantities = [];
- public StockForecastOrderingItemQuantity GetQuantity(int i) => Quantities[i];
- public double GetTotalQuantity(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
- ? Quantities.Sum(x => x.OrderTotal)
- : Quantities.Sum(x => x.JobTotal);
- public double GetRequiredQuantity(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
- ? RequiredQuantity
- : JobRequiredQuantities.Values.Sum();
- public void SetQuantities(StockForecastOrderingItemQuantity[] quantities)
- {
- Quantities = quantities;
- }
- }
- public class StockForecastOrderingResult(
- SupplierLink supplier,
- List<StockForecastOrderData.QuantityBreakup> breakups,
- StockOrderingItem item,
- double quantity,
- SupplierProduct supplierProduct)
- {
- public SupplierLink Supplier { get; set; } = supplier;
- public StockOrderingItem Item { get; set; } = item;
- public SupplierProduct SupplierProduct { get; set; } = supplierProduct;
- public double Quantity { get; set; } = quantity;
- public List<StockForecastOrderData.QuantityBreakup> Breakups { get; set; } = breakups;
- }
- public enum StockForecastOrderingStrategy
- {
- PerProduct,
- Exact,
- RoundUp,
- LowestUnitPrice,
- LowestOverallPrice,
- LowestOverstock
- }
- public class StockForecastOrderingGrid : DynamicItemsListGrid<StockOrderingItem>, ISpecificGrid
- {
- #region Internal Data + Caches
- private List<SupplierProduct> SupplierProducts = [];
- private SupplierLink[] Suppliers = [];
- private DynamicActionColumn[] SupplierProductColumns = [];
- private DynamicActionColumn[] QuantityColumns = [];
- private DynamicActionColumn[] CostColumns = [];
- private readonly Dictionary<Guid, Job> JobDetails = [];
- private readonly Dictionary<Guid, JobRequisitionItem> JobRequiDetails = [];
- #endregion
- private static BitmapImage _warning = PRSDesktop.Resources.warning.AsBitmapImage();
- #region Public Properties
- public IList<StockForecastOrderData> OrderData { get; set; }
- private StockForecastOrderingType _orderType = StockForecastOrderingType.Breakup;
- public StockForecastOrderingType OrderType
- {
- get => _orderType;
- set
- {
- if(_orderType != value)
- {
- _orderType = value;
- if (OrderData != null)
- {
- CalculateQuantities(true);
- UIComponent.UpdateOrderType(OrderType);
- foreach(var control in QuantityControls)
- {
- control.UpdateControl(OrderType);
- }
- Refresh(true, true);
- }
- }
- }
- }
- private StockForecastOrderingStrategy orderStrategy;
- public StockForecastOrderingStrategy OrderStrategy
- {
- get => orderStrategy;
- set
- {
- orderStrategy = value;
- if (OrderData != null)
- {
- foreach (var item in Items)
- {
- item.OrderStrategy = CastOrderStrategyToProductOrderStrategy(value, item.Product.OrderStrategy);
- item.CustomStrategy = false;
- }
- CalculateQuantities(false);
- Refresh(false, true);
- }
- }
- }
- public double TotalQuantity => Items.Sum(x => x.GetTotalQuantity(OrderType));
- public IEnumerable<StockForecastOrderingResult> Results
- {
- get
- {
- for(int i = 0; i < Suppliers.Length; ++i)
- {
- var supplier = Suppliers[i];
- foreach(var item in Items)
- {
- var qty = item.GetQuantity(i);
- if (qty.SupplierProduct is null)
- {
- continue;
- }
- if(OrderType == StockForecastOrderingType.StockOrder && qty.OrderTotal > 0)
- {
- yield return new(supplier, new(), item, qty.OrderTotal, qty.SupplierProduct);
- }
- else if(qty.OrderTotal > 0)
- {
- var breakups = new List<StockForecastOrderData.QuantityBreakup>();
- foreach(var (key, q) in qty.Breakups)
- {
- // Check JobID because we are to skip the empty job (this is just the difference between all the allocations and the quantity on the PO).
- if(q > 0 && key.JobID != Guid.Empty)
- {
- breakups.Add(new(key.JobID, key.RequiID, "", q));
- }
- }
- yield return new(supplier, breakups, item, qty.OrderTotal, qty.SupplierProduct);
- }
- }
- }
- }
- }
- #endregion
- public StockForecastOrderingGrid()
- {
- HiddenColumns.Add(x => x.Product.Image.ID);
- }
- private static SupplierProductOrderStrategy CastOrderStrategyToProductOrderStrategy(StockForecastOrderingStrategy strategy, SupplierProductOrderStrategy defaultValue)
- {
- return strategy switch
- {
- StockForecastOrderingStrategy.Exact => SupplierProductOrderStrategy.Exact,
- StockForecastOrderingStrategy.LowestOverallPrice => SupplierProductOrderStrategy
- .LowestOverallPrice,
- StockForecastOrderingStrategy.LowestUnitPrice => SupplierProductOrderStrategy.LowestUnitPrice,
- StockForecastOrderingStrategy.LowestOverstock => SupplierProductOrderStrategy.LowestOverstock,
- StockForecastOrderingStrategy.RoundUp => SupplierProductOrderStrategy.RoundUp,
- StockForecastOrderingStrategy.PerProduct or _ => defaultValue
- };
- }
- #region UI Component
- private Component? _uiComponent;
- private Component UIComponent
- {
- get
- {
- _uiComponent ??= new Component(this);
- return _uiComponent;
- }
- }
- protected override IDynamicGridUIComponent<StockOrderingItem> CreateUIComponent()
- {
- return UIComponent;
- }
- private class Component : DynamicGridGridUIComponent<StockOrderingItem>
- {
- private StockForecastOrderingGrid Grid;
- public Component(StockForecastOrderingGrid grid)
- {
- Parent = grid;
- Grid = grid;
- UpdateOrderType(grid.OrderType);
- }
- public void UpdateOrderType(StockForecastOrderingType type)
- {
- DataGrid.FrozenColumnCount = 8;
- }
- protected override Brush? GetCellSelectionBackgroundBrush()
- {
- return null;
- }
- protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
- {
- var item = Grid.LoadItem(row);
- if(column is DynamicActionColumn ac)
- {
- var qIdx = Grid.QuantityColumns.IndexOf(ac);
- var idx = Math.Max(qIdx, Grid.CostColumns.IndexOf(ac));
- if(idx != -1)
- {
- var supplierProduct = item.GetQuantity(idx).SupplierProduct;
- return supplierProduct is null
- ? Brushes.Gainsboro
- : Brushes.WhiteSmoke;
- }
- }
- return base.GetCellBackground(row, column);
- }
- }
- #endregion
- private bool _observing = true;
- private void SetObserving(bool observing)
- {
- _observing = observing;
- }
- protected override void Changed()
- {
- if (_observing)
- {
- base.Changed();
- }
- }
- protected override void DoReconfigure(DynamicGridOptions options)
- {
- options.Clear();
- options.FilterRows = true;
- }
- private bool _loadedData = false;
- private void LoadData()
- {
- var supplierProductColumns = Columns.None<SupplierProduct>().Add(x => x.ID)
- .Add(x => x.SupplierLink.ID)
- .Add(x => x.Product.ID)
- .Add(x => x.Style.ID)
- .Add(x => x.Style.Code)
- .Add(x => x.Job.ID)
- .Add(x => x.Job.JobNumber)
- .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(OrderData.Select(x => x.Product.ID).ToArray())
- .And(x => x.SupplierLink.ID).IsNotEqualTo(Guid.Empty),
- supplierProductColumns,
- new SortOrder<SupplierProduct>(x => x.SupplierLink.Code))
- .ToList<SupplierProduct>();
- Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).ToArray();
- CalculateQuantities(true);
- _loadedData = true;
- }
- private StockForecastOrderingItemQuantity CreateQuantity(int itemIdx)
- {
- var qty = new StockForecastOrderingItemQuantity();
- qty.Changed += () =>
- {
- if (!_observing) return;
- var row = Data.Rows[itemIdx];
- InvalidateRow(row);
- DoChanged();
- };
- return qty;
- }
- private SupplierProduct? CalculateSupplierProduct(StockOrderingItem item, int supplierIdx)
- {
- var supplierProducts = string.IsNullOrWhiteSpace(item.Dimensions.Unit.Conversion)
- ? SupplierProducts.Where(x => x.Dimensions.Equals(item.Dimensions))
- : SupplierProducts;
- 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.OrderTotal = 0;
- qty.Breakups.Clear();
- foreach(var id in item.GetJobRequiredQuantities().Keys)
- {
- qty.Breakups[id] = 0;
- }
- return supplierProduct;
- }
- private void SetSupplierProduct(StockOrderingItem item, SupplierProduct supplierProduct)
- {
- var supplierIdx = Suppliers.WithIndex()
- .FirstOrDefault(x => x.Value.ID == supplierProduct.SupplierLink.ID, new KeyValuePair<int, SupplierLink>(-1, null)).Key;
- if(supplierIdx != -1)
- {
- var qty = item.GetQuantity(supplierIdx);
- if(OrderType == StockForecastOrderingType.Breakup)
- {
- qty.Breakups.Clear();
- foreach(var (id, q) in item.GetJobRequiredQuantities())
- {
- qty.Breakups[id] = q;
- }
- }
- qty.OrderTotal = GetRequiredQuantity(item, supplierProduct);
- }
- }
- private void CalculateSupplierProduct(StockOrderingItem item)
- {
- var selectedSupplierProducts = new List<SupplierProduct>();
- for(int i = 0; i < Suppliers.Length; ++i)
- {
- var supplierProduct = CalculateSupplierProduct(item, i);
- if(supplierProduct is not null)
- {
- selectedSupplierProducts.Add(supplierProduct);
- }
- }
- var selectedSupplierProduct = SelectSupplierProduct(selectedSupplierProducts, item);
- if(selectedSupplierProduct is not null)
- {
- SetSupplierProduct(item, selectedSupplierProduct);
- }
- }
- private void CalculateQuantities(bool recreateItems)
- {
- SetObserving(false);
- if (recreateItems)
- {
- Items.Clear();
- foreach(var dataItem in OrderData)
- {
- var item = new StockOrderingItem();
- item.Product.CopyFrom(dataItem.Product);
- item.Style.CopyFrom(dataItem.Style);
- item.Dimensions.CopyFrom(dataItem.Dimensions);
- item.OrderStrategy = CastOrderStrategyToProductOrderStrategy(OrderStrategy, item.Product.OrderStrategy);
- item.RequiredQuantity = dataItem.RequiredQuantity;
- foreach(var breakup in dataItem.GetRequiredQuantities())
- {
- item.SetJobRequiredQuantity(breakup.JobID, breakup.JobRequiItemID, breakup.Quantity);
- }
- 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();
- }
- #region Order Strategy
- private double CalculateSupplierProductRequiredQuantity(StockOrderingItem item, SupplierProduct supplierProduct)
- {
- var supplierIdx = Suppliers.WithIndex().FirstOrDefault(x => x.Value.ID == supplierProduct.SupplierLink.ID).Key;
- var qty = item.GetQuantity(supplierIdx);
- var req = OrderType == StockForecastOrderingType.StockOrder ? item.RequiredQuantity : qty.Breakups.Sum(x => x.Value);
- var d = new StockDimensions();
- d.CopyFrom(supplierProduct.Dimensions);
- var result = DimensionUtils.ConvertDimensions(d, 1.0, (f,c) => Client.Query(f,c));
- req = Math.Ceiling(req / result);
- return req;
- }
- private SupplierProduct? SelectSupplierProduct(IEnumerable<SupplierProduct> supplierProducts, StockOrderingItem item)
- {
- double DimensionsRatio(SupplierProduct x)
- {
- return item.Dimensions.Value / (x.Dimensions.Value.IsEffectivelyEqual(0.0) ? (item.Dimensions.Value.IsEffectivelyEqual(0.0) ? 1.0 : item.Dimensions.Value) : x.Dimensions.Value);
- }
- switch (item.OrderStrategy)
- {
- case SupplierProductOrderStrategy.Exact:
- case SupplierProductOrderStrategy.RoundUp:
- return supplierProducts.Where(x => x.Dimensions.Equals(item.Dimensions))
- .MinBy(x => x.CostPrice);
-
- case SupplierProductOrderStrategy.LowestOverallPrice:
- return supplierProducts.MinBy(x => x.CostPrice * Math.Ceiling(CalculateSupplierProductRequiredQuantity(item, x) * DimensionsRatio(x)));
- case SupplierProductOrderStrategy.LowestUnitPrice:
- return supplierProducts.MinBy(x => x.CostPrice * DimensionsRatio(x));
-
- case SupplierProductOrderStrategy.LowestOverstock:
- return supplierProducts.MinBy(x => x.Dimensions.Value * Math.Ceiling(CalculateSupplierProductRequiredQuantity(item, x) * DimensionsRatio(x)));
-
- default:
- return null;
- }
- }
- private double GetRequiredQuantity(StockOrderingItem item, SupplierProduct supplierProduct)
- {
- var requiredQuantity = CalculateSupplierProductRequiredQuantity(item, supplierProduct);
- double DimensionsRatio(SupplierProduct x)
- {
- return item.Dimensions.Value / (x.Dimensions.Value.IsEffectivelyEqual(0.0) ? (item.Dimensions.Value.IsEffectivelyEqual(0.0) ? 1.0 : item.Dimensions.Value) : x.Dimensions.Value);
- }
- var _strategy = item.CustomStrategy
- ? SupplierProductOrderStrategy.LowestOverstock
- : item.OrderStrategy;
-
- switch (_strategy)
- {
- case SupplierProductOrderStrategy.Exact:
- return requiredQuantity;
- case SupplierProductOrderStrategy.RoundUp:
- return Math.Ceiling(requiredQuantity);
- case SupplierProductOrderStrategy.LowestOverallPrice:
- case SupplierProductOrderStrategy.LowestUnitPrice:
- case SupplierProductOrderStrategy.LowestOverstock:
- return Math.Ceiling(requiredQuantity * DimensionsRatio(supplierProduct));
- default:
- return 0.0;
- }
- }
- #endregion
- private bool _loadedColumns = false;
- protected override DynamicGridColumns LoadColumns()
- {
- if (!_loadedData)
- {
- LoadData();
- }
- var columns = new DynamicGridColumns();
- columns.Add<StockOrderingItem, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
- columns.Add<StockOrderingItem, string>(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleLeft);
- columns.Add<StockOrderingItem, string>(x => x.Dimensions.UnitSize, 80, "Size", "", Alignment.MiddleCenter);
- columns.Add<StockOrderingItem, string>(x => x.Style.Code, 80, "Style", "", Alignment.MiddleCenter);
- columns.Add<StockOrderingItem, double>(x => x.RequiredQuantity, 80, "Required", "", Alignment.MiddleCenter);
- if (!_loadedColumns)
- {
- ActionColumns.Clear();
- ActionColumns.Add(new DynamicImageColumn(Warning_Image) { Position = DynamicActionColumnPosition.Start });
- ActionColumns.Add(new DynamicImagePreviewColumn<StockOrderingItem>(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 = 140
- });
- 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;
- }
- private void EditSupplierProductGrid(DynamicGrid<SupplierProduct> grid)
- {
- grid.OnCustomiseEditor += (sender, items, column, editor) =>
- {
- if(new Column<SupplierProduct>(x => x.SupplierLink.ID).IsEqualTo(column.ColumnName)
- || new Column<SupplierProduct>(x => x.Product.ID).IsEqualTo(column.ColumnName)
- || new Column<SupplierProduct>(x => x.Style.ID).IsEqualTo(column.ColumnName))
- {
- editor.Editable = editor.Editable.Combine(Editable.Disabled);
- }
- };
- }
- private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
- {
- if (row is null) return;
- column.AddItem("New Supplier", null, row =>
- {
- if (row is null) return;
- var selection = new MultiSelectDialog<Supplier>(
- new Filter<Supplier>(x => x.ID).NotInList(Suppliers.Select(x => x.ID).ToArray()),
- Columns.None<Supplier>().Add(x => x.ID).Add(x => x.Code), multiselect: false);
- if (selection.ShowDialog() != true)
- {
- return;
- }
- var supplier = selection.Data().Rows.First().ToObject<Supplier>();
- var orderingItem = LoadItem(row);
- var supplierProduct = new SupplierProduct();
- supplierProduct.Product.CopyFrom(orderingItem.Product);
- supplierProduct.Style.CopyFrom(orderingItem.Style);
- supplierProduct.Dimensions.CopyFrom(orderingItem.Dimensions);
- supplierProduct.SupplierLink.CopyFrom(supplier);
- if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
- {
- SupplierProducts.Add(supplierProduct);
- var newSuppliers = new SupplierLink[Suppliers.Length + 1];
- var newIdx = Suppliers.Length;
- for (int i = 0; i < Suppliers.Length; i++)
- {
- newSuppliers[i] = Suppliers[i];
- }
- newSuppliers[newIdx] = supplierProduct.SupplierLink;
- foreach (var (itemIdx, item) in Items.WithIndex())
- {
- var quantities = new StockForecastOrderingItemQuantity[newSuppliers.Length];
- for (int i = 0; i < Suppliers.Length; ++i)
- {
- quantities[i] = item.GetQuantity(i);
- }
- var newQty = CreateQuantity(itemIdx);
- quantities[newIdx] = newQty;
- if(OrderType == StockForecastOrderingType.StockOrder)
- {
- newQty.OrderTotal = 0;
- }
- else
- {
- newQty.OrderTotal = 0;
- foreach(var id in item.GetJobRequiredQuantities().Keys)
- {
- newQty.Breakups[id] = 0;
- }
- }
- item.SetQuantities(quantities);
- }
- Suppliers = newSuppliers;
- foreach (var item in Items)
- {
- CalculateSupplierProduct(item, newIdx);
- }
- _loadedColumns = false;
- Refresh(true, true);
- }
- });
- }
- private BitmapImage? Warning_Image(CoreRow? row)
- {
- if (row is null) return _warning;
- var item = LoadItem(row);
- if(item.GetTotalQuantity(OrderType) < item.RequiredQuantity)
- {
- return _warning;
- }
- else
- {
- return null;
- }
- }
- protected override void ConfigureColumnGroups()
- {
- for(int idx = 0; idx < Suppliers.Length; ++idx)
- {
- GetColumnGrouping().AddGroup(Suppliers[idx].Code, SupplierProductColumns[idx], CostColumns[idx]);
- }
- }
- #region Job Data Cache
- private void LoadJobData(IEnumerable<Guid> ids)
- {
- var neededIDs = ids.Where(x => x != Guid.Empty && !JobDetails.ContainsKey(x)).ToArray();
- if(neededIDs.Length > 0)
- {
- var details = Client.Query(
- new Filter<Job>(x => x.ID).InList(neededIDs),
- Columns.None<Job>().Add(x => x.ID)
- .Add(x => x.JobNumber)
- .Add(x => x.Name));
- foreach(var job in details.ToObjects<Job>())
- {
- JobDetails[job.ID] = job;
- }
- }
- }
- private void LoadJobRequiData(IEnumerable<Guid> ids)
- {
- var neededIDs = ids.Where(x => x != Guid.Empty && !JobRequiDetails.ContainsKey(x)).ToArray();
- if(neededIDs.Length > 0)
- {
- var details = Client.Query(
- new Filter<JobRequisitionItem>(x => x.ID).InList(neededIDs),
- Columns.None<JobRequisitionItem>().Add(x => x.ID)
- .Add(x => x.Requisition.Number)
- .Add(x => x.Requisition.Description));
- foreach(var requi in details.ToObjects<JobRequisitionItem>())
- {
- JobRequiDetails[requi.ID] = requi;
- }
- }
- }
- #endregion
- private class QuantityControl : ContentControl
- {
- private readonly StockOrderingItem Item;
- private readonly int SupplierIndex;
- private readonly StockForecastOrderingGrid Parent;
- public QuantityControl(StockForecastOrderingGrid parent, StockOrderingItem item, int supplierIndex, StockForecastOrderingType mode)
- {
- Parent = parent;
- Item = item;
- SupplierIndex = supplierIndex;
- UpdateControl(mode);
- }
- public void UpdateControl(StockForecastOrderingType mode)
- {
- // If no supplier product has been selected for this cell, we can't allow the user to select a quantity.
- var supplierProduct = Item.GetQuantity(SupplierIndex).SupplierProduct;
- if(supplierProduct is null)
- {
- Content = null;
- return;
- }
- if(mode == StockForecastOrderingType.StockOrder)
- {
- 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).OrderTotal
- };
- editor.ValueChanged += (o, e) =>
- {
- Item.GetQuantity(SupplierIndex).OrderTotal = editor.Value ?? default;
- };
- Content = editor;
- }
- else if(mode == StockForecastOrderingType.Breakup)
- {
- 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 _breakups = Item.GetQuantity(SupplierIndex).Breakups.Sum(x => x.Value);
-
- var editor = new DoubleTextBox
- {
- VerticalAlignment = VerticalAlignment.Stretch,
- HorizontalAlignment = HorizontalAlignment.Stretch,
- VerticalContentAlignment = VerticalAlignment.Center,
- HorizontalContentAlignment = HorizontalAlignment.Center,
- Background = new SolidColorBrush(Colors.WhiteSmoke),
- BorderThickness = new Thickness(0.0),
- Value = Item.GetQuantity(SupplierIndex).OrderTotal,
- IsReadOnly = true,
- Focusable = false,
- };
- 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.Breakups.Keys.Select(x => x.JobID));
- Parent.LoadJobRequiData(qty.Breakups.Keys.Select(x => x.RequiID));
- var items = qty.Breakups.Select(x =>
- {
- var item = new StockForecastOrderingJobItem
- {
- JobID = x.Key.JobID,
- JobRequiID = x.Key.RequiID,
- RequiredQuantity = Item.GetJobRequiredQuantities().GetValueOrDefault(x.Key),
- Quantity = x.Value
- };
- if(item.JobID == Guid.Empty)
- {
- item.Description = "General Stock";
- }
- else if(Parent.JobDetails.TryGetValue(item.JobID, out var job))
- {
- if(Parent.JobRequiDetails.TryGetValue(item.JobRequiID, out var requi))
- {
- item.Description = $"{job.JobNumber}: Requi #{requi.Requisition.Number} ({requi.Requisition.Description})";
- }
- else
- {
- item.Description = $"{job.JobNumber}: {job.Name}";
- }
- }
- return item;
- }).ToList();
- var genitem = items.FirstOrDefault(x =>
- x.JobID == Guid.Empty && x.JobRequiID == Guid.Empty);
- if (genitem == null)
- {
- genitem = new StockForecastOrderingJobItem
- {
- Description = "General Stock",
- JobID = Guid.Empty,
- JobRequiID = Guid.Empty
- };
- items.Insert(0, genitem);
- }
- else
- {
- items.Remove(genitem);
- items.Insert(0, genitem);
- }
- var window = new StockForecastOrderJobScreen();
- window.Items = items;
- if(window.ShowDialog() == true)
- {
- foreach(var item in items)
- {
- qty.Breakups[new(item.JobID, item.JobRequiID)] = item.Quantity;
- }
- qty.DoChanged();
- var req = Parent.GetRequiredQuantity(Item, supplierProduct);
- editor.Value = req;
- qty.OrderTotal = req;
- }
- };
- grid.Children.Add(btn);
- Content = grid;
- }
- }
- }
- private List<QuantityControl> QuantityControls = [];
- private void InitialiseSupplierColumn(int idx)
- {
- var contextMenuFunc = (CoreRow[]? rows) =>
- {
- var row = rows?.FirstOrDefault();
- if (row is null) return null;
- var item = LoadItem(row);
- var menu = new ContextMenu();
- menu.AddItem("Create Supplier Product", null, new Tuple<StockOrderingItem, int>(item, idx), CreateSupplierProduct_Click);
- return menu;
- };
- var qtyColumn = new Tuple<DynamicActionColumn, QuantityControl?>(null!, null);
- SupplierProductColumns[idx] = new DynamicTemplateColumn(row =>
- {
- var instance = LoadItem(row);
- var comboBox = new ComboBox();
- comboBox.Tag = idx;
- var supplierProducts = string.IsNullOrWhiteSpace(instance.Dimensions.Unit.Conversion)
- ? SupplierProducts.Where(x => x.Dimensions.Equals(instance.Dimensions))
- : SupplierProducts;
- 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.Job.ID == Guid.Empty ? x.Dimensions.UnitSize : $"Job {x.Job.JobNumber}: {x.Dimensions.UnitSize}"));
- if (items.Any())
- items = items.Prepend(new KeyValuePair<SupplierProduct?, string>(null, ""));
- comboBox.SelectedValuePath = "Key";
- comboBox.ItemsSource = items.ToArray();
- comboBox.DisplayMemberPath = "Value";
- var qty = instance.GetQuantity(idx);
- comboBox.Bind(ComboBox.SelectedValueProperty, qty, x => x.SupplierProduct);
- comboBox.SelectionChanged += (o, e) =>
- {
- var box = o as ComboBox;
- instance.CustomStrategy = true;
- var _item = LoadItem(row);
- var _product = ((o as ComboBox)?.SelectedValue as SupplierProduct ?? new SupplierProduct());
- qty.OrderTotal = GetRequiredQuantity(_item, _product);
-
- InvalidateRow(row);
- };
- comboBox.VerticalContentAlignment = VerticalAlignment.Center;
- comboBox.Margin = new Thickness(2);
- if(!items.Any())
- comboBox.IsEnabled = false;
- return comboBox;
- })
- {
- HeaderText = "U.O.M.",
- Width = 80
- };
-
- QuantityColumns[idx] = new DynamicTemplateColumn(row =>
- {
- var instance = LoadItem(row);
- var control = new QuantityControl(this, instance, idx, OrderType);
- QuantityControls.Add(control);
- return control;
- })
- {
- HeaderText = "Qty.",
- Width = 80,
- ContextMenu = contextMenuFunc
- };
-
- CostColumns[idx] = new DynamicTextColumn(row =>
- {
- if(row is null)
- {
- return "Cost";
- }
- var instance = LoadItem(row);
- var qty = instance.GetQuantity(idx);
- if(qty.SupplierProduct is not null)
- {
- return $"{qty.OrderTotal * qty.SupplierProduct.CostPrice:C2}";
- }
- else
- {
- return "";
- }
- })
- {
- HeaderText = "Cost",
- Width = 80,
- ContextMenu = contextMenuFunc,
- GetSummary = () =>
- {
- return new DynamicGridCustomSummary((rows) => Cost_Aggregate(idx, rows), "C2");
- }
- };
- ActionColumns.Add(SupplierProductColumns[idx]);
- ActionColumns.Add(QuantityColumns[idx]);
- ActionColumns.Add(CostColumns[idx]);
- }
- private double Cost_Aggregate(int supplierIdx, IEnumerable<CoreRow> rows)
- {
- return rows.Sum(row =>
- {
- var item = LoadItem(row);
- var qty = item.GetQuantity(supplierIdx);
- if(qty.SupplierProduct is not null)
- {
- return qty.OrderTotal * qty.SupplierProduct.CostPrice;
- }
- else
- {
- return 0;
- }
- });
- }
- private void CreateSupplierProduct_Click(Tuple<StockOrderingItem, int> tuple)
- {
- var (item, supplierIdx) = tuple;
- var supplierProduct = new SupplierProduct();
- supplierProduct.Product.CopyFrom(item.Product);
- supplierProduct.Style.CopyFrom(item.Style);
- supplierProduct.Dimensions.CopyFrom(item.Dimensions);
- supplierProduct.SupplierLink.CopyFrom(Suppliers[supplierIdx]);
- if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
- {
- SupplierProducts.Add(supplierProduct);
- var qty = item.GetQuantity(supplierIdx);
- if(qty.SupplierProduct is null)
- {
- var hasSelected = item.Quantities.Any(x => x.SupplierProduct is not null);
- var sp = CalculateSupplierProduct(item, supplierIdx);
- if(!hasSelected && sp is not null)
- {
- SetSupplierProduct(item, sp);
- }
- }
- InvalidateGrid();
- }
- }
- }
|