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 Syncfusion.Windows.Shared; 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; namespace PRSDesktop.Panels.ReservationManagement.TreatmentPO; public class ReservationManagementTreatmentPOItem : BaseObject { public delegate void ChangedHandler(int idx, double quantity); public event ChangedHandler? Changed; public ProductLink TreatmentProduct { get; set; } public ProductStyleLink Finish { get; set; } public JobLink Job { get; set; } public ProductLink Product { get; set; } public ProductStyleLink Style { get; set; } public StockDimensions Dimensions { get; set; } public StockLocationLink Location { get; set; } public JobRequisitionItemLink JRI { get; set; } public double Multiplier { get; set; } [EditorSequence(4)] [DoubleEditor] public double RequiredQuantity { get; set; } private double[] Quantities = []; public double GetQuantity(int i) => Quantities[i]; public void SetQuantity(int i, double quantity) { Quantities[i] = quantity; Changed?.Invoke(i, quantity); } public double GetTotalQuantity() => Quantities.Sum(); public void SetQuantities(double[] quantities) { Quantities = quantities; } } public class StockForecastOrderingResult { public SupplierLink Supplier { get; set; } public ReservationManagementTreatmentPOItem Item { get; set; } public SupplierProduct SupplierProduct { get; set; } public double Quantity { get; set; } public StockForecastOrderingResult(SupplierLink supplier, ReservationManagementTreatmentPOItem item, double quantity, SupplierProduct supplierProduct) { Supplier = supplier; Item = item; Quantity = quantity; SupplierProduct = supplierProduct; } } public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid, ISpecificGrid { private List SupplierProducts = []; private SupplierLink[] Suppliers = []; public double TotalQuantity => Items.Sum(x => x.GetTotalQuantity()); private DynamicActionColumn[] QuantityColumns = []; private DynamicActionColumn[] CostColumns = []; private readonly Dictionary JobDetails = []; private static BitmapImage _warning = PRSDesktop.Resources.warning.AsBitmapImage(); public IEnumerable 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 == 0) { continue; } var supplierProduct = GetSupplierProduct(item, supplier.ID); if (supplierProduct is null) { continue; } yield return new(supplier, item, qty, supplierProduct); } } } } private DynamicGridCustomColumnsComponent ColumnsComponent; public ReservationManagementTreatmentOrderGrid() { HiddenColumns.Add(x => x.Product.Image.ID); ColumnsComponent = new(this, nameof(ReservationManagementTreatmentOrderGrid)); } #region UI Component private Component? _uiComponent; private Component UIComponent { get { _uiComponent ??= new Component(this); return _uiComponent; } } protected override IDynamicGridUIComponent CreateUIComponent() { return UIComponent; } private class Component : DynamicGridGridUIComponent { private ReservationManagementTreatmentOrderGrid Grid; public Component(ReservationManagementTreatmentOrderGrid grid) { Parent = grid; Grid = grid; DataGrid.FrozenColumnCount = 9; } 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 = Grid.GetSupplierProduct(item, Grid.Suppliers[idx].ID); if(supplierProduct is null) { return new SolidColorBrush(Colors.Gainsboro); } //if(item.GetTotalQuantity(Grid.OrderType) < item.RequiredQuantity) //{ // return new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }; //} //else //{ // return new SolidColorBrush(Colors.LightGreen) { Opacity = 0.5 }; //} } } 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 supplierColumns = Columns.None().Add(x => x.ID) .Add(x => x.SupplierLink.ID) .Add(x => x.Job.ID) .Add(x => x.Product.ID) .Add(x => x.CostPrice) .Add(x => x.SupplierLink.Code) .Add(x => x.Product.TaxCode.ID) .Add(x => x.TaxCode.ID); SupplierProducts = Client.Query( new Filter(x => x.Product.ID).InList(Items.Select(x => x.TreatmentProduct.ID).ToArray()) .And(x => x.SupplierLink.ID).IsNotEqualTo(Guid.Empty), supplierColumns, new SortOrder(x => x.SupplierLink.Code)) .ToList(); Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).ToArray(); foreach(var (itemIdx, item) in Items.WithIndex()) { var quantities = new double[Suppliers.Length]; for(int i = 0; i < Suppliers.Length; ++i) { quantities[i] = 0; } item.SetQuantities(quantities); item.Changed += (i, qty) => { if (!_observing) return; var row = Data.Rows[itemIdx]; foreach(var ac in ActionColumns) { if(ac != QuantityColumns[i]) { UpdateCell(row, ac); } } DoChanged(); }; } CalculateQuantities(); _loadedData = true; } private void CalculateQuantities() { SetObserving(false); foreach(var item in Items) { var supplierProduct = GetSupplierProduct(item); for(int i = 0; i < Suppliers.Length; ++i) { var supplier = Suppliers[i]; if(supplierProduct is not null && supplier.ID == supplierProduct.SupplierLink.ID) { item.SetQuantity(i, item.RequiredQuantity); } else { item.SetQuantity(i, 0); } } } SetObserving(true); DoChanged(); InvalidateGrid(); } protected override void SaveColumns(DynamicGridColumns columns) { ColumnsComponent.SaveColumns(columns); } protected override void LoadColumnsMenu(ContextMenu menu) { ColumnsComponent.LoadColumnsMenu(menu); } public override DynamicGridColumns GenerateColumns() { var columns = new DynamicGridColumns(); columns.Add(x => x.Product.Code, 120, "Product", "", Alignment.MiddleCenter); columns.Add(x => x.Style.Code, 120, "Style", "", Alignment.MiddleCenter); //columns.Add(x => x.Location.Code, 80, "Location", "", Alignment.MiddleLeft); columns.Add(x => x.Dimensions.UnitSize, 80, "Size", "", Alignment.MiddleCenter); columns.Add(x => x.TreatmentProduct.Code, 120, "Finish", "", Alignment.MiddleCenter); columns.Add(x => x.TreatmentProduct.Name, 0, "Treatment Product", "", Alignment.MiddleCenter); columns.Add(x => x.RequiredQuantity, 80, "Required", "", Alignment.MiddleCenter); columns.Add(x => x.Multiplier, 80, "Multiplier", "", Alignment.MiddleCenter); return columns; } protected override DynamicGridColumns LoadColumns() { if (!_loadedData) { LoadData(); } ActionColumns.Clear(); ActionColumns.Add(new DynamicImageColumn(Warning_Image) { Position = DynamicActionColumnPosition.Start }); ActionColumns.Add(new DynamicImagePreviewColumn(x => x.Product.Image) { Position = DynamicActionColumnPosition.Start }); var columns = ColumnsComponent.LoadColumns(); 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)); return columns; } private void EditSupplierProductGrid(DynamicGrid grid) { grid.OnCustomiseEditor += (sender, items, column, editor) => { if(new Column(x => x.SupplierLink.ID).IsEqualTo(column.ColumnName) || new Column(x => x.Product.ID).IsEqualTo(column.ColumnName) || new Column(x => x.Style.ID).IsEqualTo(column.ColumnName) || new Column(x => x.Job.ID).IsEqualTo(column.ColumnName) //|| new Column(x => x.Dimensions).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( new Filter(x => x.ID).NotInList(Suppliers.Select(x => x.ID).ToArray()), Columns.None().Add(x => x.ID).Add(x => x.Code), multiselect: false); if (selection.ShowDialog() != true) { return; } var supplier = selection.Data().Rows.First().ToObject(); var item = LoadItem(row); var supplierProduct = new SupplierProduct(); LookupFactory.DoLookup(supplierProduct, x => x.Product, item.TreatmentProduct.ID); 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, oItem) in Items.WithIndex()) { var populateSupplierProduct = GetSupplierProduct(oItem); var quantities = new double[newSuppliers.Length]; for (int i = 0; i < Suppliers.Length; ++i) { quantities[i] = oItem.GetQuantity(i); } quantities[newIdx] = 0; oItem.SetQuantities(quantities); } Suppliers = newSuppliers; Refresh(true, true); } }); } private BitmapImage? Warning_Image(CoreRow? row) { if (row is null) return _warning; var item = LoadItem(row); if(item.GetTotalQuantity() < item.RequiredQuantity) { return _warning; } else { return null; } } protected override void ConfigureColumnGroups() { for(int idx = 0; idx < Suppliers.Length; ++idx) { GetColumnGrouping().AddGroup(Suppliers[idx].Code, QuantityColumns[idx], CostColumns[idx]); } } private class QuantityControl : ContentControl { private readonly ReservationManagementTreatmentPOItem Item; private readonly int SupplierIndex; private readonly ReservationManagementTreatmentOrderGrid Parent; public QuantityControl(ReservationManagementTreatmentOrderGrid parent, ReservationManagementTreatmentPOItem item, int supplierIndex) { Parent = parent; Item = item; SupplierIndex = supplierIndex; UpdateControl(); } public void UpdateControl() { var supplierProduct = Parent.GetSupplierProduct(Item, Parent.Suppliers[SupplierIndex].ID); if(supplierProduct is null) { Content = null; return; } var editor = new DoubleTextBox { VerticalAlignment = VerticalAlignment.Stretch, HorizontalContentAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Stretch, Background = new SolidColorBrush(Colors.LightYellow), BorderThickness = new Thickness(0.0), MinValue = 0.0, Value = Item.GetQuantity(SupplierIndex) }; editor.ValueChanged += (o, e) => { var maxValue = Item.RequiredQuantity - (Item.GetTotalQuantity() - Item.GetQuantity(SupplierIndex)); var value = (double?)e.NewValue ?? default; if(value > maxValue) { Dispatcher.BeginInvoke(() => editor.Value = maxValue); } else { Item.SetQuantity(SupplierIndex, value); } }; Content = editor; } } private List QuantityControls = []; 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(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(null!, null); QuantityColumns[idx] = new DynamicTemplateColumn(row => { var instance = LoadItem(row); var control = new QuantityControl(this, instance, idx); QuantityControls.Add(control); return control; }) { HeaderText = "Qty.", Width = 60, ContextMenu = contextMenuFunc }; CostColumns[idx] = new DynamicTextColumn(row => { if(row is null) { return "Cost"; } var instance = LoadItem(row); var qty = instance.GetQuantity(idx); var supplierProduct = GetSupplierProduct(instance, Suppliers[idx].ID); if(supplierProduct is not null) { return $"{qty * supplierProduct.CostPrice * instance.Multiplier:C2}"; } else { return ""; } }) { HeaderText = "Cost", Width = 70, ContextMenu = contextMenuFunc, GetSummary = () => { return new DynamicGridCustomSummary((rows) => Cost_Aggregate(idx, rows), "C2"); } }; ActionColumns.Add(QuantityColumns[idx]); ActionColumns.Add(CostColumns[idx]); } private double Cost_Aggregate(int supplierIdx, IEnumerable rows) { return rows.Sum(row => { var item = LoadItem(row); var supplierProduct = GetSupplierProduct(item, Suppliers[supplierIdx].ID); if (supplierProduct is not null) { var qty = item.GetQuantity(supplierIdx); return qty * item.Multiplier * supplierProduct.CostPrice; } else { return 0; } }); } private void CreateSupplierProduct_Click(Tuple tuple) { var (item, supplierIdx) = tuple; var supplierProduct = new SupplierProduct(); LookupFactory.DoLookup(supplierProduct, x => x.Product, item.TreatmentProduct.ID); supplierProduct.SupplierLink.CopyFrom(Suppliers[supplierIdx]); if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid)) { SupplierProducts.Add(supplierProduct); InvalidateGrid(); } } private static bool Matches(ReservationManagementTreatmentPOItem item, SupplierProduct supplierProduct, bool matchJob) { return item.TreatmentProduct.ID == supplierProduct.Product.ID && (!matchJob || item.JRI.Job.ID == supplierProduct.Job.ID); } private SupplierProduct? GetSupplierProduct(ReservationManagementTreatmentPOItem item) { var defaultSupplierProduct = SupplierProducts.FirstOrDefault(x => x.ID == item.Product.Supplier.ID); if(defaultSupplierProduct is not null && Matches(item, defaultSupplierProduct, false)) { return defaultSupplierProduct; } else { return SupplierProducts.FirstOrDefault(x => Matches(item, x, true)) ?? SupplierProducts.FirstOrDefault(x => Matches(item, x, false)); } } private SupplierProduct? GetSupplierProduct(ReservationManagementTreatmentPOItem item, Guid supplierID) { return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(item, x, false)); } //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; } private int SupplierIndex; private ReservationManagementTreatmentOrderGrid Grid; public CostAggregate(int supplierIndex, ReservationManagementTreatmentOrderGrid grid) { SupplierIndex = supplierIndex; Grid = grid; } public Action CalculateAggregateFunc() { return AggregateFunc; } private void AggregateFunc(IEnumerable items, string property, PropertyDescriptor args) { if (items is IEnumerable rows) { } else { Logger.Send(LogType.Error, "", $"Attempting to calculate aggregate on invalid data type '{items.GetType()}'."); } } } }