Browse Source

Added functionality to select a PurchaseOrder in header when doing a Treatment PO

Kenric Nugteren 9 tháng trước cách đây
mục cha
commit
27552ff4c1

+ 47 - 103
prs.desktop/Panels/Reservation Management/ReservationManagementPanel.xaml.cs

@@ -303,9 +303,6 @@ public partial class ReservationManagementPanel : UserControl, IPanel<JobRequisi
         var window = new ReservationManagementTreatmentOrderScreen(items);
         if(window.ShowDialog() == true)
         {
-            var orders = new List<Tuple<PurchaseOrder, List<(PurchaseOrderItem, JobRequisitionItemLink, StockForecastTreatmentOrderingResult)>>>();
-            var movements = new List<StockMovement>();
-
             var results = window.Results.GroupBy(x => x.Supplier.ID).ToDictionary(x => x.Key, x => x.ToArray());
             var suppliers = Client.Query(
                 new Filter<Supplier>(x => x.ID).InList(results.Keys.ToArray()),
@@ -313,15 +310,52 @@ public partial class ReservationManagementPanel : UserControl, IPanel<JobRequisi
                 .ToObjects<Supplier>()
                 .ToDictionary(x => x.ID);
 
+            var doIssue = false;
+            if(Security.IsAllowed<CanIssueTreatmentPurchaseOrders>())
+            {
+                if (_globalSettings.AutoIssueTreatmentPOs)
+                {
+                    doIssue = true;
+                }
+                else if(MessageWindow.ShowYesNo($"Do you wish to mark the purchase order as issued?", "Mark as issued?"))
+                {
+                    doIssue = true;
+                }
+            }
+
+            var grid = new SupplierPurchaseOrders();
+            var windows = new List<ReservationManagementPanelTreatmentPOWindow>();
+
+            grid.OnAfterSave += (editor, items) =>
+            {
+                var order = items.FirstOrDefault();
+                var window = windows.FirstOrDefault(x => x.Order == order);
+                window?.AfterSave();
+            };
+
             foreach(var (supplierID, perSupplier) in results)
             {
-                var order = new PurchaseOrder();
-                order.RaisedBy.ID = App.EmployeeID;
+                var order = perSupplier.First().PurchaseOrder;
+                if(order is null)
+                {
+                    order = new PurchaseOrder();
+                    order.RaisedBy.ID = App.EmployeeID;
+
+                    order.DueDate = DateTime.Today.AddDays(7);
+                    order.Notes = [$"Treatment purchase order raised by {App.EmployeeName} from Reservation Management screen"];
 
-                order.DueDate = DateTime.Today.AddDays(7);
-                order.Notes = [$"Treatment purchase order raised by {App.EmployeeName} from Reservation Management screen"];
+                    LookupFactory.DoLookup<PurchaseOrder, Supplier, SupplierLink>(order, x => x.SupplierLink, supplierID);
+                }
+                else
+                {
+                    Client.EnsureColumns(order, DynamicGridUtils.LoadEditorColumns<PurchaseOrder>());
+                }
 
-                LookupFactory.DoLookup<PurchaseOrder, Supplier, SupplierLink>(order, x => x.SupplierLink, supplierID);
+                if (doIssue)
+                {
+                    order.IssuedBy.ID = App.EmployeeID;
+                    order.IssuedDate = DateTime.Now;
+                }
 
                 var orderItems = new List<(PurchaseOrderItem, JobRequisitionItemLink, StockForecastTreatmentOrderingResult)>();
                 foreach(var item in perSupplier)
@@ -352,92 +386,12 @@ public partial class ReservationManagementPanel : UserControl, IPanel<JobRequisi
                     orderItem.Description = $"Treatment for {item.Item.JRI.Product.Name} ({item.Item.Product.Code}/{item.Item.Product.Name})";
                 }
 
-                if(suppliers.TryGetValue(supplierID, out var supplier))
-                {
-                    foreach(var item in perSupplier)
-                    {
-                        var tOut = new StockMovement();
-                        tOut.Job.CopyFrom(item.Item.Job);
-                        tOut.Style.CopyFrom(item.Item.Style);
-                        tOut.Location.CopyFrom(item.Item.Location);
-                        tOut.Product.CopyFrom(item.Item.Product);
-                        tOut.Dimensions.CopyFrom(item.Item.Dimensions);
-
-                        tOut.Employee.ID = App.EmployeeID;
-                        tOut.Date = DateTime.Now;
-                        tOut.Issued = item.Quantity;
-                        tOut.Type = StockMovementType.TransferOut;
-                        tOut.JobRequisitionItem.CopyFrom(item.Item.JRI);
-                        tOut.Notes = "Stock movement for treatment purchase order created from Reservation Management";
-
-                        var tIn = tOut.CreateMovement();
-                        tIn.Transaction = tOut.Transaction;
-
-                        tIn.Style.CopyFrom(item.Item.JRI.Style);
-                        tIn.Location.CopyFrom(supplier.DefaultLocation);
-
-                        tIn.Employee.ID = App.EmployeeID;
-                        tIn.Date = tOut.Date;
-                        tIn.Received = item.Quantity;
-                        tIn.Type = StockMovementType.TransferIn;
-                        tIn.JobRequisitionItem.CopyFrom(item.Item.JRI);
-                        tIn.Notes = "Stock movement for treatment purchase order created from Reservation Management";
-
-                        movements.Add(tOut);
-                        movements.Add(tIn);
-                    }
-                }
-                else
-                {
-                    MessageWindow.ShowMessage(
-                        $"No default location set up for supplier '{perSupplier[0].Supplier.Code}'; skipping creating stock movements",
-                        "No default location");
-                }
-
-                orders.Add(new(order, orderItems));
-            }
-
-            var doIssue = false;
-            if(Security.IsAllowed<CanIssueTreatmentPurchaseOrders>())
-            {
-                if (_globalSettings.AutoIssueTreatmentPOs)
-                {
-                    doIssue = true;
-                }
-                else if(MessageWindow.ShowYesNo($"Do you wish to mark the purchase order{(orders.Count != 1 ? "s" : "")} as issued?", "Mark as issued?"))
-                {
-                    doIssue = true;
-                }
-            }
-            if (doIssue)
-            {
-                foreach(var (order, _) in orders)
-                {
-                    order.IssuedBy.ID = App.EmployeeID;
-                    order.IssuedDate = DateTime.Now;
-                }
-            }
-
-            var grid = new SupplierPurchaseOrders();
-            var windows = new List<ReservationManagementPanelTreatmentPOWindow>();
-            foreach(var order in orders)
-            {
-                var perSupplier = results.GetValueOrDefault(order.Item1.SupplierLink.ID) ?? [];
-
-                var editorWindow = new ReservationManagementPanelTreatmentPOWindow(grid, order, perSupplier, PanelLink);
+                var editorWindow = new ReservationManagementPanelTreatmentPOWindow(grid, new(order, orderItems), perSupplier, PanelLink);
                 editorWindow.Form.Show();
 
                 ISubPanelHost.Global.AddSubPanel(editorWindow.Form);
                 windows.Add(editorWindow);
             }
-            grid.OnAfterSave += (editor, items) =>
-            {
-                var order = items.FirstOrDefault();
-                var window = windows.FirstOrDefault(x => x.Order == order);
-                window?.AfterSave();
-            };
-
-            JobRequiItems.SelectedRows = [];
         }
     }
 
@@ -466,20 +420,7 @@ public partial class ReservationManagementPanel : UserControl, IPanel<JobRequisi
 
             Form.Form.DoChanged();
             Form.SetLayoutType<VerticalDynamicEditorGridLayout>();
-            grid.InitialiseEditorForm(Form, [order.Item1], type =>
-            {
-                if(type == typeof(PurchaseOrderItem))
-                {
-                    var table = new CoreTable();
-                    table.LoadColumns(typeof(PurchaseOrderItem));
-                    table.LoadRows(order.Item2.Select(x => x.Item1));
-                    return table;
-                }
-                else
-                {
-                    return null;
-                }
-            }, true);
+            grid.InitialiseEditorForm(Form, [order.Item1], null, true);
 
             var oneToManyPage = Form.Pages?.OfType<SupplierPurchaseOrderItemOneToMany>().FirstOrDefault();
             if(oneToManyPage is null)
@@ -488,6 +429,9 @@ public partial class ReservationManagementPanel : UserControl, IPanel<JobRequisi
             }
             else
             {
+                oneToManyPage.Items.AddRange(order.Item2.Select(x => x.Item1));
+                oneToManyPage.Refresh(false, true);
+
                 oneToManyPage.OnCustomiseEditor += (form, items, column, editor) =>
                 {
                     if(column.ColumnName == $"{nameof(PurchaseOrderItem.Product)}.{nameof(PurchaseOrderItem.Product.ID)}")

+ 204 - 132
prs.desktop/Panels/Reservation Management/Treatment PO/ReservationManagementTreatmentOrderGrid.cs

@@ -71,29 +71,35 @@ public class ReservationManagementTreatmentPOItem : BaseObject
     }
 }
 
-public class StockForecastTreatmentOrderingResult
+public class StockForecastTreatmentOrderingResult(
+    SupplierLink supplier,
+    PurchaseOrder? purchaseOrder,
+    ReservationManagementTreatmentPOItem item,
+    double quantity,
+    SupplierProduct supplierProduct)
 {
-    public SupplierLink Supplier { get; set; }
+    public SupplierLink Supplier { get; set; } = supplier;
 
-    public ReservationManagementTreatmentPOItem Item { get; set; }
+    public PurchaseOrder? PurchaseOrder { get; set; } = purchaseOrder;
 
-    public SupplierProduct SupplierProduct { get; set; }
+    public ReservationManagementTreatmentPOItem Item { get; set; } = item;
 
-    public double Quantity { get; set; }
+    public SupplierProduct SupplierProduct { get; set; } = supplierProduct;
 
-    public StockForecastTreatmentOrderingResult(SupplierLink supplier, ReservationManagementTreatmentPOItem item, double quantity, SupplierProduct supplierProduct)
-    {
-        Supplier = supplier;
-        Item = item;
-        Quantity = quantity;
-        SupplierProduct = supplierProduct;
-    }
+    public double Quantity { get; set; } = quantity;
 }
 
 public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<ReservationManagementTreatmentPOItem>, ISpecificGrid
 {
+    private class OrderSupplier(SupplierLink supplier, PurchaseOrder? purchaseOrder)
+    {
+        public SupplierLink Supplier { get; set; } = supplier;
+
+        public PurchaseOrder? PurchaseOrder { get; set; } = purchaseOrder;
+    }
+
     private List<SupplierProduct> SupplierProducts = [];
-    private SupplierLink[] Suppliers = [];
+    private OrderSupplier[] Suppliers = [];
 
     public double TotalQuantity => Items.Sum(x => x.GetTotalQuantity());
 
@@ -103,6 +109,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
     private readonly Dictionary<Guid, Job> JobDetails = [];
 
     private static BitmapImage _warning = PRSDesktop.Resources.warning.AsBitmapImage();
+    private static BitmapImage _search = PRSDesktop.Resources.menu.AsBitmapImage();
 
     public IEnumerable<StockForecastTreatmentOrderingResult> Results
     {
@@ -119,13 +126,13 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
                         continue;
                     }
 
-                    var supplierProduct = GetSupplierProduct(item, supplier.ID);
+                    var supplierProduct = GetSupplierProduct(item, supplier.Supplier.ID);
                     if (supplierProduct is null)
                     {
                         continue;
                     }
                     
-                    yield return new(supplier, item, qty, supplierProduct);
+                    yield return new(supplier.Supplier, supplier.PurchaseOrder, item, qty, supplierProduct);
                 }
             }
         }
@@ -181,7 +188,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
                 var idx = Math.Max(qIdx, Grid.CostColumns.IndexOf(ac));
                 if(idx != -1)
                 {
-                    var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[idx].ID);
+                    var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[idx].Supplier.ID);
                     if(supplierProduct is null)
                     {
                         return new SolidColorBrush(Colors.Gainsboro);
@@ -198,10 +205,83 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
             }
             return base.GetCellBackground(row, column);
         }
+
+        protected override Style GetColumnGroupHeaderCellStyle(DynamicGridColumnGroup group)
+        {
+            var style = base.GetColumnGroupHeaderCellStyle(group);
+            if(group.Tag is int index)
+            {
+                style.AddSetter(Control.HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch);
+                style.AddSetter(Control.VerticalContentAlignmentProperty, VerticalAlignment.Stretch);
+                style.AddSetter(Control.PaddingProperty, new Thickness(0.0));
+                style.AddSetter(GridStackedHeaderCellControl.ContentTemplateProperty, TemplateGenerator.CreateDataTemplate(() =>
+                {
+                    var dock = new DockPanel { LastChildFill = true };
+
+                    var button = new Button();
+                    button.Content = new Image
+                    {
+                        Source = _search
+                    };
+                    button.Width = 25;
+                    button.BorderBrush = Colors.Silver.ToBrush();
+                    button.BorderThickness = new Thickness(0.75, 0.0, 0.0, 0.0);
+                    //button.Background = Colors.WhiteSmoke.ToBrush();
+                    DockPanel.SetDock(button, Dock.Right);
+                    dock.Children.Add(button);
+
+                    var border = new Border();
+                    border.Background = Colors.LightYellow.ToBrush();
+                    border.Margin = new Thickness(0.0, 0.0, 0.0, 0.0);
+                    border.BorderThickness = new(0.0);
+
+                    var textBox = new TextBox();
+                    textBox.IsEnabled = false;
+                    textBox.VerticalContentAlignment = VerticalAlignment.Center;
+                    textBox.HorizontalContentAlignment = HorizontalAlignment.Center;
+                    textBox.Padding = new Thickness(2.0, 0.0, 0.0, 0.0);
+                    textBox.Background = Colors.LightYellow.ToBrush();
+                    textBox.BorderThickness = new(0.0);
+                    textBox.Text = Grid.Suppliers[index].PurchaseOrder?.PONumber ?? "(New Order)";
+
+                    border.Child = textBox;
+
+                    DockPanel.SetDock(border, Dock.Left);
+                    dock.Children.Add(border);
+
+                    button.Click += (o, e) =>
+                    {
+                        var supplier = Grid.Suppliers[index];
+                        var dlg = new MultiSelectDialog<PurchaseOrder>(
+                            new Filter<PurchaseOrder>(x => x.ClosedDate).IsEqualTo(DateTime.MinValue)
+                                .And(x => x.SupplierLink.ID).IsEqualTo(supplier.Supplier.ID),
+                            Columns.None<PurchaseOrder>().Add(x => x.ID).Add(x => x.PONumber),
+                            multiselect: false);
+                        var result = dlg.ShowDialog();
+                        if(result == true)
+                        {
+                            var item = dlg.Data().ToObjects<PurchaseOrder>()?.FirstOrDefault();
+                            supplier.PurchaseOrder = item;
+                            textBox.Text = item?.PONumber ?? "(New Order)";
+                        }
+                        else if(result == false)
+                        {
+                            supplier.PurchaseOrder = null;
+                            textBox.Text = "(New Order)";
+                        }
+                    };
+
+                    return dock;
+                }));
+            }
+            return style;
+        }
     }
 
     #endregion
 
+    #region Grid Stuff
+
     private bool _observing = true;
     private void SetObserving(bool observing)
     {
@@ -221,6 +301,10 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
         options.FilterRows = true; 
     }
 
+    #endregion
+
+    #region Data
+
     private bool _loadedData = false;
     private void LoadData()
     {
@@ -240,7 +324,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
             new SortOrder<SupplierProduct>(x => x.SupplierLink.Code))
             .ToList<SupplierProduct>();
 
-        Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).ToArray();
+        Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).Select(x => new OrderSupplier(x, null)).ToArray();
 
         foreach(var (itemIdx, item) in Items.WithIndex())
         {
@@ -281,7 +365,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
             for(int i = 0; i < Suppliers.Length; ++i)
             {
                 var supplier = Suppliers[i];
-                if(supplierProduct is not null && supplier.ID == supplierProduct.SupplierLink.ID)
+                if(supplierProduct is not null && supplier.Supplier.ID == supplierProduct.SupplierLink.ID)
                 {
                     item.SetQuantity(i, item.RequiredQuantity);
                 }
@@ -297,6 +381,10 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
         InvalidateGrid();
     }
 
+    #endregion
+
+    #region Columns
+
     protected override void SaveColumns(DynamicGridColumns columns)
     {
         ColumnsComponent.SaveColumns(columns);
@@ -352,76 +440,21 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
         return columns;
     }
 
-    private void EditSupplierProductGrid(DynamicGrid<SupplierProduct> grid)
+    protected override void ConfigureColumnGroups()
     {
-        grid.OnCustomiseEditor += (sender, items, column, editor) =>
+        // The first set of header groups is for the supplier Codes
+        var codeGroup = GetColumnGrouping();
+        for(int idx = 0; idx < Suppliers.Length; ++idx)
         {
-            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)
-                || new Column<SupplierProduct>(x => x.Job.ID).IsEqualTo(column.ColumnName)
-                //|| new Column<SupplierProduct>(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;
+            codeGroup.AddGroup(Suppliers[idx].Supplier.Code, QuantityColumns[idx], CostColumns[idx]);
+        }
 
-        column.AddItem("New Supplier", null, row =>
+        // The next set of header groups is for the purchase order combo-boxes.
+        var poGroup = AddColumnGrouping();
+        for(int idx = 0; idx < Suppliers.Length; ++idx)
         {
-            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 item = LoadItem(row);
-
-            var supplierProduct = new SupplierProduct();
-            LookupFactory.DoLookup<SupplierProduct, Product, ProductLink>(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);
-            }
-        });
+            poGroup.AddGroup(Suppliers[idx].Supplier.Code, QuantityColumns[idx], CostColumns[idx], tag: idx);
+        }
     }
 
     private BitmapImage? Warning_Image(CoreRow? row)
@@ -439,14 +472,6 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
         }
     }
 
-    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;
@@ -464,7 +489,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
 
         public void UpdateControl()
         {
-            var supplierProduct = Parent.GetSupplierProduct(Item, Parent.Suppliers[SupplierIndex].ID);
+            var supplierProduct = Parent.GetSupplierProduct(Item, Parent.Suppliers[SupplierIndex].Supplier.ID);
             if(supplierProduct is null)
             {
                 Content = null;
@@ -502,7 +527,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
 
     private void InitialiseSupplierColumn(int idx)
     {
-        var supplierProducts = SupplierProducts.Where(x => x.SupplierLink.ID == Suppliers[idx].ID).ToArray();
+        var supplierProducts = SupplierProducts.Where(x => x.SupplierLink.ID == Suppliers[idx].Supplier.ID).ToArray();
 
         var contextMenuFunc = (CoreRow[]? rows) =>
         {
@@ -510,7 +535,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
             if (row is null) return null;
 
             var item = LoadItem(row);
-            var supplierProduct = GetSupplierProduct(item, Suppliers[idx].ID);
+            var supplierProduct = GetSupplierProduct(item, Suppliers[idx].Supplier.ID);
             if (supplierProduct is not null)
             {
                 return null;
@@ -544,7 +569,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
 
             var instance = LoadItem(row);
             var qty = instance.GetQuantity(idx);
-            var supplierProduct = GetSupplierProduct(instance, Suppliers[idx].ID);
+            var supplierProduct = GetSupplierProduct(instance, Suppliers[idx].Supplier.ID);
             if(supplierProduct is not null)
             {
                 return $"{qty * supplierProduct.CostPrice * instance.Multiplier:C2}";
@@ -572,7 +597,7 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
         return rows.Sum(row =>
         {
             var item = LoadItem(row);
-            var supplierProduct = GetSupplierProduct(item, Suppliers[supplierIdx].ID);
+            var supplierProduct = GetSupplierProduct(item, Suppliers[supplierIdx].Supplier.ID);
             if (supplierProduct is not null)
             {
                 var qty = item.GetQuantity(supplierIdx);
@@ -585,13 +610,93 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
         });
     }
 
+    #endregion
+
+    #region New Suppliers
+
+    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)
+                || new Column<SupplierProduct>(x => x.Job.ID).IsEqualTo(column.ColumnName)
+                //|| new Column<SupplierProduct>(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<Supplier>(
+                new Filter<Supplier>(x => x.ID).NotInList(Suppliers.ToArray(x => x.Supplier.ID)),
+                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 item = LoadItem(row);
+
+            var supplierProduct = new SupplierProduct();
+            LookupFactory.DoLookup<SupplierProduct, Product, ProductLink>(supplierProduct, x => x.Product, item.TreatmentProduct.ID);
+            supplierProduct.SupplierLink.CopyFrom(supplier);
+
+            if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
+            {
+                SupplierProducts.Add(supplierProduct);
+                var newSuppliers = new OrderSupplier[Suppliers.Length + 1];
+                var newIdx = Suppliers.Length;
+
+                for (int i = 0; i < Suppliers.Length; i++)
+                {
+                    newSuppliers[i] = Suppliers[i];
+                }
+                newSuppliers[newIdx] = new(supplierProduct.SupplierLink, null);
+
+                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);
+            }
+        });
+    }
+
+    #endregion
+
+    #region Supplier Product Matching
+
     private void CreateSupplierProduct_Click(Tuple<ReservationManagementTreatmentPOItem, int> tuple)
     {
         var (item, supplierIdx) = tuple;
 
         var supplierProduct = new SupplierProduct();
         LookupFactory.DoLookup<SupplierProduct, Product, ProductLink>(supplierProduct, x => x.Product, item.TreatmentProduct.ID);
-        supplierProduct.SupplierLink.CopyFrom(Suppliers[supplierIdx]);
+        supplierProduct.SupplierLink.CopyFrom(Suppliers[supplierIdx].Supplier);
 
         if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
         {
@@ -623,39 +728,6 @@ public class ReservationManagementTreatmentOrderGrid: DynamicItemsListGrid<Reser
     {
         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<IEnumerable, string, PropertyDescriptor> CalculateAggregateFunc()
-        {
-            return AggregateFunc;
-        }
-
-        private void AggregateFunc(IEnumerable items, string property, PropertyDescriptor args)
-        {
-            if (items is IEnumerable<DataRowView> rows)
-            {
-            }
-            else
-            {
-                Logger.Send(LogType.Error, "", $"Attempting to calculate aggregate on invalid data type '{items.GetType()}'.");
-            }
-        }
-    }
+    #endregion
 }