Quellcode durchsuchen

Added internal cache for OneToMany columns, and updated HiddenColumns

Kenric Nugteren vor 1 Jahr
Ursprung
Commit
930b5f81a8

+ 7 - 0
prs.desktop/Grids/DigitalFormSubscriptionsGrid.cs

@@ -11,6 +11,13 @@ namespace PRSDesktop
 {
     class DigitalFormSubscriptionsGrid : DynamicOneToManyGrid<Employee, EmployeeFormSubscription>
     {
+        protected override void Init()
+        {
+            base.Init();
+
+            HiddenColumns.Add(x => x.Form.ID);
+        }
+
         protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
         {
             var formIDs = Items.Select(x => x.Form.ID).ToArray();

+ 86 - 87
prs.desktop/Grids/QAFormQuestionGrid.cs

@@ -8,104 +8,103 @@ using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
 
-namespace PRSDesktop.Configuration
-{
-    public abstract class QAFormQuestionGrid<T> : DynamicOneToManyGrid<DigitalForm, QAQuestion> where T : Entity, IRemotable, IPersistent, new()
-    {
-        protected bool Preview = false;
-
-        public QAFormQuestionGrid()
-        {
-            AddButton("Fill", PRSDesktop.Resources.quality.AsBitmapImage(), PreviewForm);
-            //AddButton("Design", PRSDesktop.Resources.edit.AsBitmapImage(true), DesignForm);
-        }
-
-        public virtual string Description()
-        {
-            return "QA Checks";
-        }
-
-        //private bool DesignForm(Button arg1, CoreRow[] arg2)
-        //{
-        //    var form = new DynamicFormWindow();
-        //    bool result = form.ShowDialog() == true;
-        //    return false;
-        //}
+namespace PRSDesktop.Configuration;
 
-        private bool PreviewForm(Button sender, CoreRow[] rows)
-        {
-            var window = new Window { Title = "Quality Assurance Form", Width = 700 };
-            window.Background = new SolidColorBrush(Colors.WhiteSmoke);
-
-            var layout = new Grid();
-            layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1.0F, GridUnitType.Star) });
-            layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(100.0F) });
-            layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(100.0F) });
-            layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1.0F, GridUnitType.Star) });
-            layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50.0F) });
-
-
-            var qagrid = new QAGrid();
-
-            var values = new Dictionary<Guid, object>();
-            qagrid.LoadChecks(Description(), Items, values);
-            qagrid.CollapseMargins();
-            qagrid.SetValue(Grid.RowProperty, 0);
-            qagrid.SetValue(Grid.ColumnProperty, 0);
-            qagrid.SetValue(Grid.ColumnSpanProperty, 3);
-            layout.Children.Add(qagrid);
-
-            var ok = new Button();
-            ok.SetValue(Grid.RowProperty, 1);
-            ok.SetValue(Grid.ColumnProperty, 1);
-            ok.Content = "OK";
-            ok.Margin = new Thickness(0, 0, 10, 10);
-            ok.Click += (o, e) =>
-            {
-                values.Clear();
-                window.DialogResult = true;
-                window.Close();
-            };
-            layout.Children.Add(ok);
-
-            var cancel = new Button();
-            cancel.SetValue(Grid.RowProperty, 1);
-            cancel.SetValue(Grid.ColumnProperty, 2);
-            cancel.Content = "Cancel";
-            cancel.Margin = new Thickness(0, 0, 10, 10);
-            cancel.Click += (o, e) =>
-            {
-                window.DialogResult = false;
-                window.Close();
-            };
-            layout.Children.Add(cancel);
-            window.Content = layout;
-            window.ShowDialog();
-            return false;
-        }
-    }
+public abstract class QAFormQuestionGrid<T> : DynamicOneToManyGrid<DigitalForm, QAQuestion> where T : Entity, IRemotable, IPersistent, new()
+{
+    protected bool Preview = false;
 
-    public class QAFormQuestionGrid : QAFormQuestionGrid<DigitalForm>
+    public QAFormQuestionGrid()
     {
+        AddButton("Fill", PRSDesktop.Resources.quality.AsBitmapImage(), PreviewForm);
+        //AddButton("Design", PRSDesktop.Resources.edit.AsBitmapImage(true), DesignForm);
     }
 
-    public class KanbanQAQuestionGrid : QAFormQuestionGrid<Kanban>
+    public virtual string Description()
     {
+        return "QA Checks";
     }
 
-    public class ITPQAQuestionGrid : QAFormQuestionGrid<JobITP>
-    {
-    }
+    //private bool DesignForm(Button arg1, CoreRow[] arg2)
+    //{
+    //    var form = new DynamicFormWindow();
+    //    bool result = form.ShowDialog() == true;
+    //    return false;
+    //}
 
-    public class AssignmentQAQuestionGrid : QAFormQuestionGrid<Assignment>
+    private bool PreviewForm(Button sender, CoreRow[] rows)
     {
+        var window = new Window { Title = "Quality Assurance Form", Width = 700 };
+        window.Background = new SolidColorBrush(Colors.WhiteSmoke);
+
+        var layout = new Grid();
+        layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1.0F, GridUnitType.Star) });
+        layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(100.0F) });
+        layout.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(100.0F) });
+        layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1.0F, GridUnitType.Star) });
+        layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50.0F) });
+
+
+        var qagrid = new QAGrid();
+
+        var values = new Dictionary<Guid, object>();
+        qagrid.LoadChecks(Description(), Items, values);
+        qagrid.CollapseMargins();
+        qagrid.SetValue(Grid.RowProperty, 0);
+        qagrid.SetValue(Grid.ColumnProperty, 0);
+        qagrid.SetValue(Grid.ColumnSpanProperty, 3);
+        layout.Children.Add(qagrid);
+
+        var ok = new Button();
+        ok.SetValue(Grid.RowProperty, 1);
+        ok.SetValue(Grid.ColumnProperty, 1);
+        ok.Content = "OK";
+        ok.Margin = new Thickness(0, 0, 10, 10);
+        ok.Click += (o, e) =>
+        {
+            values.Clear();
+            window.DialogResult = true;
+            window.Close();
+        };
+        layout.Children.Add(ok);
+
+        var cancel = new Button();
+        cancel.SetValue(Grid.RowProperty, 1);
+        cancel.SetValue(Grid.ColumnProperty, 2);
+        cancel.Content = "Cancel";
+        cancel.Margin = new Thickness(0, 0, 10, 10);
+        cancel.Click += (o, e) =>
+        {
+            window.DialogResult = false;
+            window.Close();
+        };
+        layout.Children.Add(cancel);
+        window.Content = layout;
+        window.ShowDialog();
+        return false;
     }
+}
 
-    public class ManufacturingSectionQAQuestionGrid : QAFormQuestionGrid<ManufacturingSection>
-    {
-    }
+public class QAFormQuestionGrid : QAFormQuestionGrid<DigitalForm>
+{
+}
 
-    public class ManufacturingPacketStageQAQuestionGrid : QAFormQuestionGrid<ManufacturingPacketStage>
-    {
-    }
+public class KanbanQAQuestionGrid : QAFormQuestionGrid<Kanban>
+{
+}
+
+public class ITPQAQuestionGrid : QAFormQuestionGrid<JobITP>
+{
+}
+
+public class AssignmentQAQuestionGrid : QAFormQuestionGrid<Assignment>
+{
+}
+
+public class ManufacturingSectionQAQuestionGrid : QAFormQuestionGrid<ManufacturingSection>
+{
+}
+
+public class ManufacturingPacketStageQAQuestionGrid : QAFormQuestionGrid<ManufacturingPacketStage>
+{
 }

+ 7 - 7
prs.desktop/Panels/Employees/Rosters/EmployeeRosterItemGrid.cs

@@ -40,18 +40,18 @@ public class EmployeeRosterItemGrid : BaseEmployeeRosterItemGrid<Employee, Emplo
     public override void SaveItem(EmployeeRosterItem item)
     {
         base.SaveItem(item);
-        new Client<EmployeeRosterItem>().Save(item, "Updated by User");
+        Client.Save(item, "Updated by User");
         UpdateRoster(null);
     }
 
-    private void UpdateRoster(IEmployeeRoster? roster)
+    private void UpdateRoster(EmployeeRoster? roster)
     {
         if (Item.RosterTemplate.ID != (roster?.ID ?? Guid.Empty))
         {
             Item.RosterTemplate.ID = roster?.ID ?? Guid.Empty;
             Item.RosterTemplate.Code = roster?.Code ?? "";
             Item.RosterTemplate.Description = roster?.Description ?? "";
-            new Client<Employee>().Save(Item, "Roster Template Updated");
+            Client.Save(Item, "Roster Template Updated");
             UpdateButtons();
         }
     }
@@ -60,7 +60,7 @@ public class EmployeeRosterItemGrid : BaseEmployeeRosterItemGrid<Employee, Emplo
     {
         base.DeleteItems(rows);
         var deletes = rows.Select(x => x.ToObject<EmployeeRosterItem>()).OfType<EmployeeRosterItem>().ToArray();
-        new Client<EmployeeRosterItem>().Delete(deletes,"Deleted by User");
+        Client.Delete(deletes,"Deleted by User");
         UpdateRoster(null);
     }
     
@@ -73,7 +73,7 @@ public class EmployeeRosterItemGrid : BaseEmployeeRosterItemGrid<Employee, Emplo
         if (dlg.ShowDialog() && dlg.Items().FirstOrDefault() is EmployeeRoster roster)
         {
             var query = new MultiQuery();
-            new Client<EmployeeRosterItem>().Delete(Items,"");
+            Client.Delete(Items,"");
             
             if (roster.ID != Guid.Empty)
             {
@@ -83,7 +83,7 @@ public class EmployeeRosterItemGrid : BaseEmployeeRosterItemGrid<Employee, Emplo
                     .Select(x => x.ToObject<EmployeeRosterTemplateItem>().ToEmployeeRosterItem(Item))
                     .ToArray();
                 
-                new Client<EmployeeRosterItem>().Save(newroster,"");
+                Client.Save(newroster,"");
             }
             
             UpdateRoster(roster);
@@ -99,7 +99,7 @@ public class EmployeeRosterItemGrid : BaseEmployeeRosterItemGrid<Employee, Emplo
         if (DateEdit.Execute("Select Start Date", ref date))
         {
             Item.RosterStart = date;
-            new Client<Employee>().Save(Item, "Updated Roster Start Date");
+            Client.Save(Item, "Updated Roster Start Date");
             DoChanged();
             return true;
         }

+ 68 - 68
prs.desktop/Panels/GPSTrackers/GPSTrackerLocationGrid.cs

@@ -9,89 +9,89 @@ using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+internal class GPSTrackerLocationGrid : DynamicOneToManyGrid<GPSTracker, GPSTrackerLocation>
 {
-    internal class GPSTrackerLocationGrid : DynamicOneToManyGrid<GPSTracker, GPSTrackerLocation>
+    private static List<GPSTrackerLocation> _cache;
+
+    protected override void Init()
     {
-        private static List<Tuple<double, double, string>> _cache;
+        base.Init();
+        Criteria.Add(
+            new Filter<GPSTrackerLocation>(x => x.Location.Timestamp).IsGreaterThanOrEqualTo(DateTime.Now.AddDays(-1))
+        );
 
-        protected override void Init()
-        {
-            base.Init();
-            Criteria.Add(
-                new Filter<GPSTrackerLocation>(x => x.Location.Timestamp).IsGreaterThanOrEqualTo(DateTime.Now.AddDays(-1))
-            );
+        HiddenColumns.Add(x => x.Location.Longitude);
+        HiddenColumns.Add(x => x.Location.Latitude);
+        HiddenColumns.Add(x => x.Location.Address);
 
-            ActionColumns.Add(new DynamicMapColumn<GPSTrackerLocation>(this, x => x.Location));
+        ActionColumns.Add(new DynamicMapColumn<GPSTrackerLocation>(this, x => x.Location));
 
-            AddButton("Get Addresses", null, GetAddressClick);
-        }
+        AddButton("Get Addresses", null, GetAddressClick);
+    }
 
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
-        {
-            base.DoReconfigure(options);
-            options.AddRange(
-                DynamicGridOption.SelectColumns,
-                DynamicGridOption.RecordCount,
-                DynamicGridOption.FilterRows
-            );
-        }
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        options.AddRange(
+            DynamicGridOption.SelectColumns,
+            DynamicGridOption.RecordCount,
+            DynamicGridOption.FilterRows
+        );
+    }
 
-        public string ReverseGeocode(double latitude, double longitude)
+    public static string ReverseGeocode(double latitude, double longitude)
+    {
+        _cache ??= Client.Query(
+                new Filter<GPSTrackerLocation>(x => x.Location.Address).IsNotEqualTo(""),
+                new Columns<GPSTrackerLocation>(
+                    x => x.Location.Address,
+                    x => x.Location.Longitude,
+                    x => x.Location.Latitude))
+            .ToList<GPSTrackerLocation>();
+        var tuple = _cache.FirstOrDefault(x => Equals(x.Location.Latitude, latitude) && Equals(x.Location.Longitude, longitude));
+        if (tuple == null)
         {
-            if (_cache == null)
-                _cache = new Client<GPSTrackerLocation>().Query(
-                    new Filter<GPSTrackerLocation>(x => x.Location.Address).IsNotEqualTo(""),
-                    new Columns<GPSTrackerLocation>(
-                        x => x.Location.Address,
-                        x => x.Location.Longitude,
-                        x => x.Location.Latitude
-                    )
-                ).Rows.Select(r => new Tuple<double, double, string>(
-                    r.Get<GPSTrackerLocation, double>(c => c.Location.Latitude),
-                    r.Get<GPSTrackerLocation, double>(c => c.Location.Longitude),
-                    r.Get<GPSTrackerLocation, string>(c => c.Location.Address)
-                )).ToList();
-            var tuple = _cache.FirstOrDefault(x => Equals(x.Item1, latitude) && Equals(x.Item2, longitude));
-            if (tuple == null)
+            var address = StoreUtils.ReverseGeocode(latitude, longitude);
+            if (!string.IsNullOrWhiteSpace(address))
             {
-                var address = StoreUtils.ReverseGeocode(latitude, longitude);
-                if (!string.IsNullOrWhiteSpace(address))
-                {
-                    tuple = new Tuple<double, double, string>(latitude, longitude, address);
-                    _cache.Add(tuple);
-                }
+                tuple = new GPSTrackerLocation();
+                tuple.Location.Latitude = latitude;
+                tuple.Location.Longitude = longitude;
+                tuple.Location.Address = address;
+                _cache.Add(tuple);
             }
-
-            return tuple != null ? tuple.Item3 : "";
         }
 
-        private bool GetAddressClick(Button arg1, CoreRow[] arg2)
+        return tuple != null ? tuple.Location.Address : "";
+    }
+
+    private bool GetAddressClick(Button arg1, CoreRow[] arg2)
+    {
+        var result = false;
+        var rows = Data.Rows.Where(r =>
+            string.IsNullOrWhiteSpace(r.Get<GPSTrackerLocation, string>(c => c.Location.Address))
+            && !Equals(r.Get<GPSTrackerLocation, double>(c => c.Location.Latitude), 0.0F)
+            && !Equals(r.Get<GPSTrackerLocation, double>(c => c.Location.Longitude), 0.0F)
+        );
+        Progress.ShowModal("Updating Addresses", progress =>
         {
-            var result = false;
-            var rows = Data.Rows.Where(r =>
-                string.IsNullOrWhiteSpace(r.Get<GPSTrackerLocation, string>(c => c.Location.Address))
-                && !Equals(r.Get<GPSTrackerLocation, double>(c => c.Location.Latitude), 0.0F)
-                && !Equals(r.Get<GPSTrackerLocation, double>(c => c.Location.Longitude), 0.0F)
-            );
-            Progress.ShowModal("Updating Addresses", progress =>
+            foreach (var row in rows)
             {
-                foreach (var row in rows)
+                var item = LoadItem(row);
+                var address = ReverseGeocode(
+                    row.Get<GPSTrackerLocation, double>(x => x.Location.Latitude),
+                    row.Get<GPSTrackerLocation, double>(x => x.Location.Longitude)
+                );
+                if (!string.IsNullOrWhiteSpace(address))
                 {
-                    var item = LoadItem(row);
-                    var address = ReverseGeocode(
-                        row.Get<GPSTrackerLocation, double>(x => x.Location.Latitude),
-                        row.Get<GPSTrackerLocation, double>(x => x.Location.Longitude)
-                    );
-                    if (!string.IsNullOrWhiteSpace(address))
-                    {
-                        item.Location.Address = address;
-                        UpdateRow<GPSTrackerLocation, string>(row, x => x.Location.Address, address, false);
-                        result = true;
-                    }
+                    item.Location.Address = address;
+                    UpdateRow<GPSTrackerLocation, string>(row, x => x.Location.Address, address, false);
+                    result = true;
                 }
-            });
-            return result;
-        }
+            }
+        });
+        return result;
     }
 }

+ 5 - 3
prs.desktop/Panels/Manufacturing/ManufacturingTemplateStageGrid.cs

@@ -19,9 +19,11 @@ namespace PRSDesktop.Panels.Manufacturing
         private bool AddAll(Button arg1, CoreRow[] arg2)
         {
             var template = Item;
-            var sections = new Client<ManufacturingSection>().Load(
-                new Filter<ManufacturingSection>(x => x.Factory.ID).IsEqualTo(template.Factory.ID)
-            ).OrderBy(x => x.Factory.Name).ThenBy(x => x.Sequence);
+            var sections = Client.Query(
+                new Filter<ManufacturingSection>(x => x.Factory.ID).IsEqualTo(template.Factory.ID),
+                null).ToObjects<ManufacturingSection>()
+                .OrderBy(x => x.Factory.Name)
+                .ThenBy(x => x.Sequence);
             foreach (var section in sections)
             {
                 var stage = new ManufacturingTemplateStage();

+ 0 - 42
prs.desktop/Panels/Products/Reservation Management/JobRequisitionItemPurchaseOrderItemGrid.cs

@@ -1,42 +0,0 @@
-using Comal.Classes;
-using InABox.Clients;
-using InABox.Core;
-using InABox.DynamicGrid;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PRSDesktop;
-
-public class JobRequisitionItemPurchaseOrderItemGrid : DynamicOneToManyGrid<JobRequisitionItem, JobRequisitionItemPurchaseOrderItem>
-{
-    public override void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
-    {
-        Reconfigure();
-        Refresh(true, false);
-        base.Load(item, type =>
-        {
-            var data = PageDataHandler?.Invoke(type);
-            if (data is null && type == typeof(JobRequisitionItemPurchaseOrderItem))
-            {
-                Filter<JobRequisitionItemPurchaseOrderItem> filter;
-                if (Item.ID == Guid.Empty)
-                {
-                    filter = new Filter<JobRequisitionItemPurchaseOrderItem>().None();
-                }
-                else
-                {
-                    filter = new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.JobRequisitionItem.ID).IsEqualTo(Item.ID);
-                }
-
-                data = new Client<JobRequisitionItemPurchaseOrderItem>().Query(
-                    filter,
-                    DynamicGridUtils.LoadEditorColumns(DataColumns()),
-                    LookupFactory.DefineSort<JobRequisitionItemPurchaseOrderItem>());
-            }
-            return data;
-        });
-    }
-}

+ 29 - 33
prs.desktop/Panels/Security/Groups/SecurityGroupUserGrid.cs

@@ -1,7 +1,4 @@
-using com.sun.org.apache.xpath.@internal.axes;
-using com.sun.security.ntlm;
-using com.sun.xml.@internal.bind.annotation;
-using Comal.Classes;
+using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
 using InABox.DynamicGrid;
@@ -11,41 +8,40 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class SecurityGroupUserGrid : DynamicOneToManyGrid<SecurityGroup, User>
 {
-    public class SecurityGroupUserGrid : DynamicOneToManyGrid<SecurityGroup, User>
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
     {
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
-        {
-            base.DoReconfigure(options);
-            options.BeginUpdate()
-                .Clear()
-                .AddRange(DynamicGridOption.AddRows)
-                .AddRange(DynamicGridOption.DeleteRows)
-                .AddRange(DynamicGridOption.MultiSelect)
-                .EndUpdate();
-        }
-
-        protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
-        {
-            var IDs = Items.Select(x => x.ID).ToArray();
+        base.DoReconfigure(options);
+        options.BeginUpdate()
+            .Clear()
+            .AddRange(DynamicGridOption.AddRows)
+            .AddRange(DynamicGridOption.DeleteRows)
+            .AddRange(DynamicGridOption.MultiSelect)
+            .EndUpdate();
+    }
 
-            var filters = new Filters<User>();
-            filters.Add(LookupFactory.DefineFilter<User>());
-            filters.Add(new Filter<User>(x => x.ID).NotInList(IDs));
+    protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
+    {
+        var IDs = Items.Select(x => x.ID).ToArray();
 
-            var dlg = new MultiSelectDialog<User>(filters.Combine(), null, true);
-            if (dlg.ShowDialog())
-            {
-                SaveItems(dlg.Items());
-                Refresh(false, true);
-            }
-        }
+        var filters = new Filters<User>();
+        filters.Add(LookupFactory.DefineFilter<User>());
+        filters.Add(new Filter<User>(x => x.ID).NotInList(IDs));
 
-        protected override void OnDeleteItem(User item)
+        var dlg = new MultiSelectDialog<User>(filters.Combine(), null, true);
+        if (dlg.ShowDialog())
         {
-            item.SecurityGroup.ID = Guid.Empty;
-            new Client<User>().Save(item, "Cleared security group");
+            SaveItems(dlg.Items());
+            Refresh(false, true);
         }
     }
+
+    protected override void OnDeleteItem(User item)
+    {
+        item.SecurityGroup.ID = Guid.Empty;
+        Client.Save(item, "Cleared security group");
+    }
 }

+ 0 - 28
prs.desktop/Panels/Suppliers/Bills/SupplierBillLineGrid.cs

@@ -92,23 +92,6 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
             .EndUpdate();
     }
 
-    public override void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
-    {
-        Refresh(true, false);
-        base.Load(item, type =>
-        {
-            var data = PageDataHandler?.Invoke(type);
-            if(data is null && type == typeof(BillLine))
-            {
-                data = new Client<BillLine>().Query(
-                    new Filter<BillLine>(x => x.BillLink.ID).IsEqualTo(Item.ID),
-                    DynamicGridUtils.LoadEditorColumns(DataColumns()),
-                    LookupFactory.DefineSort<BillLine>());
-            }
-            return data;
-        });
-    }
-
     private bool BillLineEdit_Click(CoreRow? row)
     {
         if(row is null)
@@ -124,17 +107,6 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
         return false;
     }
 
-    public override void ConfigureColumns(DynamicGridColumns columns)
-    {
-        base.ConfigureColumns(columns);
-
-        // var orderItemColumn = columns.Find(x => x.ColumnName == $"{nameof(BillLine.OrderItem)}.{nameof(BillLine.OrderItem.ID)}");
-        // if (orderItemColumn != null)
-        // {
-        //     orderItemColumn.Editor.Editable = Editable.DisabledOnDirectEdit;
-        // }
-    }
-
     private bool ImportPOLines(Button arg1, CoreRow[] arg2)
     {
         var poItems = ExtractValues(x => x.OrderItem.ID, Selection.All).ToArray();

+ 416 - 446
prs.desktop/Panels/Suppliers/PurchaseOrders/SupplierPurchaseOrderItemOneToMany.cs

@@ -10,546 +10,516 @@ using InABox.Clients;
 using InABox.Core;
 using InABox.DynamicGrid;
 using InABox.WPF;
-using NPOI.SS.Formula.Functions;
 using static InABox.DynamicGrid.DynamicMenuColumn;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class SupplierPurchaseOrderItemOneToMany : DynamicOneToManyGrid<PurchaseOrder,PurchaseOrderItem>
 {
-    public class SupplierPurchaseOrderItemOneToMany : DynamicOneToManyGrid<PurchaseOrder,PurchaseOrderItem>
-    {
-        private Button bill;
-        private Button? createConsignment;
-        //private Button? viewconsign;
-        private Button receive;
-        private Button assignLocation;
+    private Button bill;
+    private Button? createConsignment;
+    //private Button? viewconsign;
+    private Button receive;
+    private Button assignLocation;
 
-        private List<Tuple<PurchaseOrderItem, JobRequisitionItemPurchaseOrderItem>> JobRequisitionItems = new();
+    private List<Tuple<PurchaseOrderItem, JobRequisitionItemPurchaseOrderItem>> JobRequisitionItems = new();
 
-        public SupplierPurchaseOrderItemOneToMany() : base()
+    public SupplierPurchaseOrderItemOneToMany() : base()
+    {
+        HiddenColumns.Add(x => x.ID);
+
+        HiddenColumns.Add(x => x.Description);
+        HiddenColumns.Add(x => x.TaxRate);
+        HiddenColumns.Add(x => x.ExTax);
+        HiddenColumns.Add(x => x.Tax);
+        HiddenColumns.Add(x => x.IncTax);
+        HiddenColumns.Add(x => x.ReceivedDate);
+        HiddenColumns.Add(x => x.Qty);
+        HiddenColumns.Add(x => x.Balance);
+        HiddenColumns.Add(x => x.PORevision);
+        HiddenColumns.Add(x => x.DueDate);
+        HiddenColumns.Add(x => x.SupplierCode);
+
+        HiddenColumns.Add(x => x.BillLine.ID);
+        HiddenColumns.Add(x => x.Consignment.ID);
+
+        HiddenColumns.Add(x => x.Job.ID);
+        HiddenColumns.Add(x => x.StockLocation.ID);
+        HiddenColumns.Add(x => x.PurchaseGL.ID);
+        HiddenColumns.Add(x => x.CostCentre.ID);
+        HiddenColumns.Add(x => x.Product.ID);
+        HiddenColumns.Add(x => x.Product.Code);
+        HiddenColumns.Add(x => x.Product.Name);
+        HiddenColumns.Add(x => x.Style.ID);
+
+        HiddenColumns.Add(x => x.TaxCode.ID);
+        HiddenColumns.Add(x => x.TaxCode.Code);
+        HiddenColumns.Add(x => x.TaxCode.Description);
+        HiddenColumns.Add(x => x.TaxCode.Rate);
+
+        foreach (var column in new Columns<PurchaseOrderItem>()
+            .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
+            .GetColumns())
         {
-            HiddenColumns.Add(x => x.ID);
-
-            HiddenColumns.Add(x => x.BillLine.ID);
-            HiddenColumns.Add(x => x.Consignment.ID);
-
-            HiddenColumns.Add(x => x.Job.ID);
-            HiddenColumns.Add(x => x.StockLocation.ID);
-            HiddenColumns.Add(x => x.PurchaseGL.ID);
-            HiddenColumns.Add(x => x.CostCentre.ID);
-            HiddenColumns.Add(x => x.Product.ID);
-            HiddenColumns.Add(x => x.Product.Code);
-            HiddenColumns.Add(x => x.Product.Name);
-            HiddenColumns.Add(x => x.Style.ID);
-            HiddenColumns.Add(x => x.Description);
-            HiddenColumns.Add(x => x.TaxCode.ID);
-            HiddenColumns.Add(x => x.TaxCode.Code);
-            HiddenColumns.Add(x => x.TaxCode.Description);
-            HiddenColumns.Add(x => x.TaxCode.Rate);
-            HiddenColumns.Add(x => x.TaxRate);
-            HiddenColumns.Add(x => x.ExTax);
-            HiddenColumns.Add(x => x.Tax);
-            HiddenColumns.Add(x => x.IncTax);
-            HiddenColumns.Add(x => x.ReceivedDate);
-            HiddenColumns.Add(x => x.Qty);
-            HiddenColumns.Add(x => x.Balance);
-            HiddenColumns.Add(x => x.PORevision);
-            HiddenColumns.Add(x => x.DueDate);
-
-            HiddenColumns.Add(x => x.SupplierCode);
-            foreach (var column in new Columns<PurchaseOrderItem>()
-                .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
-                .GetColumns())
-            {
-                HiddenColumns.Add(column);
-            }
+            HiddenColumns.Add(column);
+        }
 
-            HiddenColumns.Add(x => x.PurchaseOrderLink.SupplierLink.ID);
-            HiddenColumns.Add(x => x.PurchaseOrderLink.Category.ID);
+        HiddenColumns.Add(x => x.PurchaseOrderLink.SupplierLink.ID);
+        HiddenColumns.Add(x => x.PurchaseOrderLink.Category.ID);
 
-            HiddenColumns.Add(x => x.Product.DigitalForm.ID);
-            HiddenColumns.Add(x => x.Product.DigitalForm.Description);
+        HiddenColumns.Add(x => x.Product.DigitalForm.ID);
+        HiddenColumns.Add(x => x.Product.DigitalForm.Description);
 
-            HiddenColumns.Add(x => x.Product.Image.ID);
-            HiddenColumns.Add(x => x.Product.Image.FileName);
-            ActionColumns.Add(new DynamicImageManagerColumn<PurchaseOrderItem>(this, x => x.Product.Image, true)
-            { Position = DynamicActionColumnPosition.Start });
+        HiddenColumns.Add(x => x.Product.Image.ID);
+        HiddenColumns.Add(x => x.Product.Image.FileName);
+        ActionColumns.Add(new DynamicImageManagerColumn<PurchaseOrderItem>(this, x => x.Product.Image, true)
+        { Position = DynamicActionColumnPosition.Start });
 
-            HiddenColumns.Add(x => x.FormCount);
-            HiddenColumns.Add(x => x.OpenForms);
-            ActionColumns.Add(new DynamicMenuColumn(BuildFormsMenu) { Position = DynamicActionColumnPosition.End });
-            ActionColumns.Add(new DynamicImageColumn(FormsImage) { Position = DynamicActionColumnPosition.Start, ToolTip = FormsToolTip });
-        }
+        HiddenColumns.Add(x => x.FormCount);
+        HiddenColumns.Add(x => x.OpenForms);
+        ActionColumns.Add(new DynamicMenuColumn(BuildFormsMenu) { Position = DynamicActionColumnPosition.End });
+        ActionColumns.Add(new DynamicImageColumn(FormsImage) { Position = DynamicActionColumnPosition.Start, ToolTip = FormsToolTip });
+    }
 
-        protected override void Init()
+    protected override void Init()
+    {
+        base.Init();
+
+        if (Security.IsAllowed<CanViewConsignmentModule>())
         {
-            base.Init();
+            createConsignment = AddButton("Add to Consignment", null, AddToConsignment);
+            createConsignment.IsEnabled = false;
+        }
 
-            if (Security.IsAllowed<CanViewConsignmentModule>())
-            {
-                createConsignment = AddButton("Add to Consignment", null, AddToConsignment);
-                createConsignment.IsEnabled = false;
-            }
+        receive = AddButton("Receive Items", null, ReceiveItems);
+        receive.IsEnabled = false;
 
-            receive = AddButton("Receive Items", null, ReceiveItems);
-            receive.IsEnabled = false;
+        bill = AddButton("Enter Bill", null, EnterBill);
+        bill.IsEnabled = false;
 
-            bill = AddButton("Enter Bill", null, EnterBill);
-            bill.IsEnabled = false;
+        assignLocation = AddButton("Assign Location", null, AssignLocation);
+    }
 
-            assignLocation = AddButton("Assign Location", null, AssignLocation);
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        if (!ReadOnly && Security.CanEdit<PurchaseOrderItem>())
+        {
+            options.Add(DynamicGridOption.DirectEdit);
+            options.Add(DynamicGridOption.DragTarget);
         }
-
-        protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+        if (!IsDirectEditMode(options))
         {
-            base.DoReconfigure(options);
-            if (!ReadOnly && Security.CanEdit<PurchaseOrderItem>())
-            {
-                options.Add(DynamicGridOption.DirectEdit);
-                options.Add(DynamicGridOption.DragTarget);
-            }
-            if (!IsDirectEditMode(options))
-            {
-                options.Add(DynamicGridOption.FilterRows);
-            }
+            options.Add(DynamicGridOption.FilterRows);
         }
+    }
 
-        protected override void OnAfterRefresh()
-        {
-            base.OnAfterRefresh();
+    protected override void OnAfterRefresh()
+    {
+        base.OnAfterRefresh();
 
-            JobRequisitionItems.RemoveAll(x => !Items.Contains(x.Item1));
-        }
+        JobRequisitionItems.RemoveAll(x => !Items.Contains(x.Item1));
+    }
 
-        public override void AfterSave(object item)
-        {
-            base.AfterSave(item);
+    public override void AfterSave(object item)
+    {
+        base.AfterSave(item);
 
-            var toSave = new List<JobRequisitionItemPurchaseOrderItem>();
-            foreach (var poItem in Items)
+        var toSave = new List<JobRequisitionItemPurchaseOrderItem>();
+        foreach (var poItem in Items)
+        {
+            var jriPois = JobRequisitionItems.Where(x => x.Item1 == poItem).Select(x => x.Item2);
+            foreach (var jriPoi in jriPois)
             {
-                var jriPois = JobRequisitionItems.Where(x => x.Item1 == poItem).Select(x => x.Item2);
-                foreach (var jriPoi in jriPois)
-                {
-                    jriPoi.PurchaseOrderItem.ID = poItem.ID;
-                }
-                toSave.AddRange(jriPois);
+                jriPoi.PurchaseOrderItem.ID = poItem.ID;
             }
-            Client.Save(toSave, "");
+            toSave.AddRange(jriPois);
         }
+        Client.Save(toSave, "");
+    }
 
-        public override void Load(object item, Func<Type, CoreTable?>? PageDataHandler)
+    private void BuildFormsMenu(DynamicMenuColumn column, CoreRow? row)
+    {
+        if (row == null) return;
+
+        if (Security.CanEdit<PurchaseOrderItem>())
         {
-            Reconfigure();
-            Refresh(true, false);
-            base.Load(item, type =>
-            {
-                var data = PageDataHandler?.Invoke(type);
-                if (data is null && type == typeof(PurchaseOrderItem))
-                {
-                    Filter<PurchaseOrderItem> filter;
-                    if (Item.ID == Guid.Empty)
-                    {
-                        filter = new Filter<PurchaseOrderItem>().None();
-                    }
-                    else
-                    {
-                        filter = new Filter<PurchaseOrderItem>(x => x.PurchaseOrderLink.ID).IsEqualTo(Item.ID);
-                    }
-
-                    data = new Client<PurchaseOrderItem>().Query(
-                        filter,
-                        DynamicGridUtils.LoadEditorColumns(DataColumns()),
-                        LookupFactory.DefineSort<PurchaseOrderItem>());
-                }
-                return data;
-            });
+            column.AddItem("Split Line", PRSDesktop.Resources.split, SplitLine, enabled: row.Get<PurchaseOrderItem, DateTime>(x => x.ReceivedDate).IsEmpty());
         }
 
-        private void BuildFormsMenu(DynamicMenuColumn column, CoreRow? row)
+        if(row.Get<PurchaseOrderItem, Guid>(x => x.Consignment.ID) != Guid.Empty)
+        {
+            column.AddItem("View Consignment", null, ViewConsignment);
+        }
+
+        var formsItem = column.AddItem("Digital Forms", PRSDesktop.Resources.kanban, null);
+        DynamicGridUtils.PopulateFormMenu<PurchaseOrderItemForm, PurchaseOrderItem, PurchaseOrderItemLink>(
+            formsItem,
+            row.Get<PurchaseOrderItem, Guid>(x => x.ID),
+            row.ToObject<PurchaseOrderItem>);
+    }
+
+    private void SplitLine(CoreRow? row)
+    {
+        if (row is null)
+            return;
+
+        var qty = row.Get<PurchaseOrderItem, double>(x => x.Qty);
+        var value = qty / 2;
+        if(DoubleEdit.Execute("Enter quantity to split on:", 0.0, qty, ref value))
         {
-            if (row == null) return;
+            var poi = LoadItem(row);
 
-            if (Security.CanEdit<PurchaseOrderItem>())
+            IEnumerable<Guid> jobRequiItemIDs;
+            if(poi.ID == Guid.Empty)
             {
-                column.AddItem("Split Line", PRSDesktop.Resources.split, SplitLine, enabled: row.Get<PurchaseOrderItem, DateTime>(x => x.ReceivedDate).IsEmpty());
+                // If not saved yet, we will have any jriPOIs in the transient list.
+                jobRequiItemIDs = JobRequisitionItems.Where(x => x.Item1 == poi).Select(x => x.Item2.JobRequisitionItem.ID).ToList();
             }
-
-            if(row.Get<PurchaseOrderItem, Guid>(x => x.Consignment.ID) != Guid.Empty)
+            else
             {
-                column.AddItem("View Consignment", null, ViewConsignment);
+                // Otherwise, they'll all be in the database.
+                jobRequiItemIDs = Client.Query(
+                    new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.PurchaseOrderItem.ID).IsEqualTo(poi.ID),
+                    new Columns<JobRequisitionItemPurchaseOrderItem>(x => x.JobRequisitionItem.ID))
+                    .ExtractValues<JobRequisitionItemPurchaseOrderItem, Guid>(x => x.JobRequisitionItem.ID);
             }
 
-            var formsItem = column.AddItem("Digital Forms", PRSDesktop.Resources.kanban, null);
-            DynamicGridUtils.PopulateFormMenu<PurchaseOrderItemForm, PurchaseOrderItem, PurchaseOrderItemLink>(
-                formsItem,
-                row.Get<PurchaseOrderItem, Guid>(x => x.ID),
-                row.ToObject<PurchaseOrderItem>);
-        }
-
-        private void SplitLine(CoreRow? row)
-        {
-            if (row is null)
-                return;
-
-            var qty = row.Get<PurchaseOrderItem, double>(x => x.Qty);
-            var value = qty / 2;
-            if(DoubleEdit.Execute("Enter quantity to split on:", 0.0, qty, ref value))
+            var newLine = new PurchaseOrderItem
+            {
+            };
+            newLine.BillLine.ID = poi.BillLine.ID;
+            newLine.BillLine.Synchronise(poi.BillLine);
+            newLine.StockLocation.ID = poi.StockLocation.ID;
+            newLine.StockLocation.Synchronise(poi.StockLocation);
+            newLine.Consignment.ID = poi.Consignment.ID;
+            newLine.Consignment.Synchronise(poi.Consignment);
+            newLine.PurchaseGL.ID = poi.PurchaseGL.ID;
+            newLine.PurchaseGL.Synchronise(poi.PurchaseGL);
+            newLine.CostCentre.ID = poi.CostCentre.ID;
+            newLine.CostCentre.Synchronise(poi.CostCentre);
+            newLine.Product.ID = poi.Product.ID;
+            newLine.Product.Synchronise(poi.Product);
+            newLine.Style.ID = poi.Style.ID;
+            newLine.Style.Synchronise(poi.Style);
+            newLine.TaxCode.ID = poi.TaxCode.ID;
+            newLine.TaxCode.Synchronise(poi.TaxCode);
+            newLine.PurchaseOrderLink.ID = poi.PurchaseOrderLink.ID;
+            newLine.PurchaseOrderLink.Synchronise(poi.PurchaseOrderLink);
+            newLine.Job.ID = poi.Job.ID;
+            newLine.Job.Synchronise(poi.Job);
+            newLine.Dimensions.CopyFrom(poi.Dimensions);
+
+            newLine.Description = poi.Description;
+            newLine.TaxRate = poi.TaxRate;
+            newLine.ExTax = poi.ExTax;
+            newLine.Tax = poi.Tax;
+            newLine.IncTax = poi.IncTax;
+            newLine.Cost = poi.Cost;
+            newLine.Balance = poi.Balance;
+            newLine.PORevision = poi.PORevision;
+            newLine.DueDate = poi.DueDate;
+            newLine.SupplierCode = poi.SupplierCode;
+
+            poi.Qty = value;
+            newLine.Qty = qty - value;
+
+            foreach(var jriID in jobRequiItemIDs)
             {
-                var poi = LoadItem(row);
+                // Add to a list to be saved later.
+                var jriPoi = new JobRequisitionItemPurchaseOrderItem();
+                jriPoi.JobRequisitionItem.ID = jriID;
+                JobRequisitionItems.Add(new(newLine, jriPoi));
+            }
 
-                IEnumerable<Guid> jobRequiItemIDs;
-                if(poi.ID == Guid.Empty)
-                {
-                    // If not saved yet, we will have any jriPOIs in the transient list.
-                    jobRequiItemIDs = JobRequisitionItems.Where(x => x.Item1 == poi).Select(x => x.Item2.JobRequisitionItem.ID).ToList();
-                }
-                else
-                {
-                    // Otherwise, they'll all be in the database.
-                    jobRequiItemIDs = Client.Query(
-                        new Filter<JobRequisitionItemPurchaseOrderItem>(x => x.PurchaseOrderItem.ID).IsEqualTo(poi.ID),
-                        new Columns<JobRequisitionItemPurchaseOrderItem>(x => x.JobRequisitionItem.ID))
-                        .ExtractValues<JobRequisitionItemPurchaseOrderItem, Guid>(x => x.JobRequisitionItem.ID);
-                }
+            SaveItem(poi);
+            SaveItem(newLine);
+            Refresh(false, true);
+            DoChanged();
+        }
+    }
 
-                var newLine = new PurchaseOrderItem
-                {
-                };
-                newLine.BillLine.ID = poi.BillLine.ID;
-                newLine.BillLine.Synchronise(poi.BillLine);
-                newLine.StockLocation.ID = poi.StockLocation.ID;
-                newLine.StockLocation.Synchronise(poi.StockLocation);
-                newLine.Consignment.ID = poi.Consignment.ID;
-                newLine.Consignment.Synchronise(poi.Consignment);
-                newLine.PurchaseGL.ID = poi.PurchaseGL.ID;
-                newLine.PurchaseGL.Synchronise(poi.PurchaseGL);
-                newLine.CostCentre.ID = poi.CostCentre.ID;
-                newLine.CostCentre.Synchronise(poi.CostCentre);
-                newLine.Product.ID = poi.Product.ID;
-                newLine.Product.Synchronise(poi.Product);
-                newLine.Style.ID = poi.Style.ID;
-                newLine.Style.Synchronise(poi.Style);
-                newLine.TaxCode.ID = poi.TaxCode.ID;
-                newLine.TaxCode.Synchronise(poi.TaxCode);
-                newLine.PurchaseOrderLink.ID = poi.PurchaseOrderLink.ID;
-                newLine.PurchaseOrderLink.Synchronise(poi.PurchaseOrderLink);
-                newLine.Job.ID = poi.Job.ID;
-                newLine.Job.Synchronise(poi.Job);
-                newLine.Dimensions.CopyFrom(poi.Dimensions);
-
-                newLine.Description = poi.Description;
-                newLine.TaxRate = poi.TaxRate;
-                newLine.ExTax = poi.ExTax;
-                newLine.Tax = poi.Tax;
-                newLine.IncTax = poi.IncTax;
-                newLine.Cost = poi.Cost;
-                newLine.Balance = poi.Balance;
-                newLine.PORevision = poi.PORevision;
-                newLine.DueDate = poi.DueDate;
-                newLine.SupplierCode = poi.SupplierCode;
-
-                poi.Qty = value;
-                newLine.Qty = qty - value;
-
-                foreach(var jriID in jobRequiItemIDs)
-                {
-                    // Add to a list to be saved later.
-                    var jriPoi = new JobRequisitionItemPurchaseOrderItem();
-                    jriPoi.JobRequisitionItem.ID = jriID;
-                    JobRequisitionItems.Add(new(newLine, jriPoi));
-                }
+    private void ViewConsignment(CoreRow? row)
+    {
+        if (row is null) return;
+
+        var consignmentID = row.Get<PurchaseOrderItem, Guid>(x => x.Consignment.ID);
+        var consignments = Client.Query(
+            new Filter<Consignment>(x => x.ID).IsEqualTo(consignmentID),
+            DynamicGridUtils.LoadEditorColumns(new Columns<Consignment>()))
+            .ToObjects<Consignment>().ToArray();
+        DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), typeof(Consignment)).EditItems(consignments);
+    }
 
-                SaveItem(poi);
-                SaveItem(newLine);
-                Refresh(false, true);
-                DoChanged();
-            }
-        }
+    private FrameworkElement? FormsToolTip(DynamicActionColumn arg1, CoreRow? arg2)
+    {
+        var text = arg2 == null || arg2.Get<PurchaseOrderItem, Guid>(x => x.Product.DigitalForm.ID) == Guid.Empty
+            ? ""
+            : arg2.Get<PurchaseOrderItem, int>(c => c.FormCount) == 0
+                ? "No forms found for this item"
+                : arg2.Get<PurchaseOrderItem, int>(c => c.OpenForms) > 0
+                    ? "Incomplete forms found"
+                    : "All forms completed";
+        return string.IsNullOrWhiteSpace(text) ? null : arg1.TextToolTip(text);
+    }
 
-        private void ViewConsignment(CoreRow? row)
-        {
-            if (row is null) return;
-
-            var consignmentID = row.Get<PurchaseOrderItem, Guid>(x => x.Consignment.ID);
-            var consignments = new Client<Consignment>()
-                .Query(
-                    new Filter<Consignment>(x => x.ID).IsEqualTo(consignmentID),
-                    DynamicGridUtils.LoadEditorColumns(new Columns<Consignment>()))
-                .ToObjects<Consignment>().ToArray();
-            DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), typeof(Consignment)).EditItems(consignments);
-        }
+    private BitmapImage? FormsImage(CoreRow? arg)
+    {
+        if (arg == null)
+            return PRSDesktop.Resources.quality.AsBitmapImage();
+        return arg.Get<PurchaseOrderItem, Guid>(x => x.Product.DigitalForm.ID) == Guid.Empty
+            ? null
+            : arg.Get<PurchaseOrderItem, int>(c => c.FormCount) == 0
+                ? PRSDesktop.Resources.warning.AsBitmapImage()
+                : arg.Get<PurchaseOrderItem, int>(c => c.OpenForms) > 0
+                    ? PRSDesktop.Resources.warning.AsBitmapImage()
+                    : PRSDesktop.Resources.quality.AsBitmapImage();
+    }
 
-        private FrameworkElement? FormsToolTip(DynamicActionColumn arg1, CoreRow? arg2)
+    private bool AddToConsignment(Button sender, CoreRow[] rows)
+    {
+        if (!rows.Any())
         {
-            var text = arg2 == null || arg2.Get<PurchaseOrderItem, Guid>(x => x.Product.DigitalForm.ID) == Guid.Empty
-                ? ""
-                : arg2.Get<PurchaseOrderItem, int>(c => c.FormCount) == 0
-                    ? "No forms found for this item"
-                    : arg2.Get<PurchaseOrderItem, int>(c => c.OpenForms) > 0
-                        ? "Incomplete forms found"
-                        : "All forms completed";
-            return string.IsNullOrWhiteSpace(text) ? null : arg1.TextToolTip(text);
+            MessageBox.Show("Please select a row first");
+            return false;
         }
 
-        private BitmapImage? FormsImage(CoreRow? arg)
+        var poItems = LoadItems(rows);
+        if(poItems.Any(x => x.ID == Guid.Empty))
         {
-            if (arg == null)
-                return PRSDesktop.Resources.quality.AsBitmapImage();
-            return arg.Get<PurchaseOrderItem, Guid>(x => x.Product.DigitalForm.ID) == Guid.Empty
-                ? null
-                : arg.Get<PurchaseOrderItem, int>(c => c.FormCount) == 0
-                    ? PRSDesktop.Resources.warning.AsBitmapImage()
-                    : arg.Get<PurchaseOrderItem, int>(c => c.OpenForms) > 0
-                        ? PRSDesktop.Resources.warning.AsBitmapImage()
-                        : PRSDesktop.Resources.quality.AsBitmapImage();
+            MessageBox.Show("Please save this purchase order first.");
+            return false;
         }
 
-        private bool AddToConsignment(Button sender, CoreRow[] rows)
+        var menu = new ContextMenu();
+        menu.AddItem("New Consignment", null, () =>
         {
-            if (!rows.Any())
-            {
-                MessageBox.Show("Please select a row first");
-                return false;
-            }
+            var consign = new Consignment();
+            consign.Supplier.ID = rows.First().Get<PurchaseOrderItem, Guid>(x => x.PurchaseOrderLink.SupplierLink.ID);
+            consign.Category.ID = rows.First().Get<PurchaseOrderItem, Guid>(x => x.PurchaseOrderLink.Category.ID);
 
-            var poItems = LoadItems(rows);
-            if(poItems.Any(x => x.ID == Guid.Empty))
+            if (new DynamicDataGrid<Consignment>().EditItems(new[] { consign }, LoadConsignmentLines, true))
             {
-                MessageBox.Show("Please save this purchase order first.");
-                return false;
-            }
-
-            var menu = new ContextMenu();
-            menu.AddItem("New Consignment", null, () =>
-            {
-                var consign = new Consignment();
-                consign.Supplier.ID = rows.First().Get<PurchaseOrderItem, Guid>(x => x.PurchaseOrderLink.SupplierLink.ID);
-                consign.Category.ID = rows.First().Get<PurchaseOrderItem, Guid>(x => x.PurchaseOrderLink.Category.ID);
-
-                if (new DynamicDataGrid<Consignment>().EditItems(new[] { consign }, LoadConsignmentLines, true))
+                foreach (var item in poItems)
                 {
-                    foreach (var item in poItems)
-                    {
-                        item.Consignment.ID = consign.ID;
-                    }
-                    new Client<PurchaseOrderItem>().Save(poItems, "Added to new consignment");
-                    Refresh(false, true);
+                    item.Consignment.ID = consign.ID;
                 }
-            });
-            menu.AddItem("Existing Consignment", null, () =>
+                new Client<PurchaseOrderItem>().Save(poItems, "Added to new consignment");
+                Refresh(false, true);
+            }
+        });
+        menu.AddItem("Existing Consignment", null, () =>
+        {
+            var popupList = new PopupList(typeof(Consignment), Guid.Empty, Array.Empty<string>());
+            popupList.OnDefineFilter += type =>
             {
-                var popupList = new PopupList(typeof(Consignment), Guid.Empty, Array.Empty<string>());
-                popupList.OnDefineFilter += type =>
-                {
-                    return new Filter<Consignment>(x => x.Closed).IsNotEqualTo(DateTime.MinValue);
-                };
-                if (popupList.ShowDialog() == true)
+                return new Filter<Consignment>(x => x.Closed).IsNotEqualTo(DateTime.MinValue);
+            };
+            if (popupList.ShowDialog() == true)
+            {
+                foreach (var item in poItems)
                 {
-                    foreach (var item in poItems)
-                    {
-                        item.Consignment.ID = popupList.ID;
-                    }
-                    new Client<PurchaseOrderItem>().Save(poItems, "Added to existing consignment");
-                    Refresh(false, true);
+                    item.Consignment.ID = popupList.ID;
                 }
-            });
-            menu.IsOpen = true;
+                new Client<PurchaseOrderItem>().Save(poItems, "Added to existing consignment");
+                Refresh(false, true);
+            }
+        });
+        menu.IsOpen = true;
 
-            return false;
-        }
+        return false;
+    }
 
-        private CoreTable? LoadConsignmentLines(Type type)
+    private CoreTable? LoadConsignmentLines(Type type)
+    {
+        if (type == typeof(PurchaseOrderItem))
         {
-            if (type == typeof(PurchaseOrderItem))
-            {
-                var result = new CoreTable();
-                result.LoadColumns(typeof(PurchaseOrderItem));
-                result.LoadRows(SelectedRows);
-                return result;
-            }
-            else
-            {
-                return null;
-            }
+            var result = new CoreTable();
+            result.LoadColumns(typeof(PurchaseOrderItem));
+            result.LoadRows(SelectedRows);
+            return result;
         }
-
-        private static bool EnterBill(Button sender, CoreRow[] rows)
+        else
         {
-            if (!rows.Any())
-            {
-                MessageBox.Show("Please select a row first");
-                return false;
-            }
-
-            var bill = new Bill();
-            bill.SupplierLink.ID = rows.First()
-                .Get<PurchaseOrderItem, Guid>(x => x.PurchaseOrderLink.SupplierLink.ID);
-            bill.BillDate = DateTime.Today;
-            return new DynamicDataGrid<Bill>().EditItems(new[] { bill }, (type) =>
-            {
-                return LoadBillLines(type, rows);
-            }, true);
+            return null;
         }
-        private static CoreTable LoadBillLines(Type type, CoreRow[] rows)
-        {
-            var result = new CoreTable();
-            result.LoadColumns(typeof(BillLine));
-            foreach (var row in rows)
-            {
-                var billrow = result.NewRow();
-                billrow.Set<BillLine, Guid>(x => x.OrderItem.ID, row.Get<PurchaseOrderItem, Guid>(x => x.ID));
-
-                var description = new List<string>();
-                if (row.Get<PurchaseOrderItem, Guid>(x => x.Product.ID) != Guid.Empty)
-                    description.Add(string.Format("{0} : {1}", row.Get<PurchaseOrderItem, string>(x => x.Product.Code),
-                        row.Get<PurchaseOrderItem, string>(x => x.Product.Name)));
-                var Description = row.Get<PurchaseOrderItem, string>(x => x.Description);
-                if (!string.IsNullOrEmpty(Description))
-                    description.Add(Description);
-                billrow.Set<BillLine, string>(x => x.Description, string.Join("\n", description));
-
-                billrow.Set<BillLine, Guid>(x => x.TaxCode.ID, row.Get<PurchaseOrderItem, Guid>(x => x.TaxCode.ID));
-                billrow.Set<BillLine, string>(x => x.TaxCode.Code, row.Get<PurchaseOrderItem, string>(x => x.TaxCode.Code));
-                billrow.Set<BillLine, string>(x => x.TaxCode.Description, row.Get<PurchaseOrderItem, string>(x => x.TaxCode.Description));
-                billrow.Set<BillLine, double>(x => x.TaxCode.Rate, row.Get<PurchaseOrderItem, double>(x => x.TaxCode.Rate));
-                billrow.Set<BillLine, double>(x => x.TaxRate, row.Get<PurchaseOrderItem, double>(x => x.TaxRate));
-                billrow.Set<BillLine, double>(x => x.ExTax, row.Get<PurchaseOrderItem, double>(x => x.ExTax));
-                billrow.Set<BillLine, double>(x => x.Tax, row.Get<PurchaseOrderItem, double>(x => x.Tax));
-                billrow.Set<BillLine, double>(x => x.IncTax, row.Get<PurchaseOrderItem, double>(x => x.IncTax));
-                result.Rows.Add(billrow);
-            }
+    }
 
-            return result;
+    private static bool EnterBill(Button sender, CoreRow[] rows)
+    {
+        if (!rows.Any())
+        {
+            MessageBox.Show("Please select a row first");
+            return false;
         }
 
-        private bool ReceiveItems(Button sender, CoreRow[] rows)
+        var bill = new Bill();
+        bill.SupplierLink.ID = rows.First()
+            .Get<PurchaseOrderItem, Guid>(x => x.PurchaseOrderLink.SupplierLink.ID);
+        bill.BillDate = DateTime.Today;
+        return new DynamicDataGrid<Bill>().EditItems(new[] { bill }, (type) =>
         {
-            if (!rows.Any())
-            {
-                MessageBox.Show("Please select a row first");
-                return false;
-            }
+            return LoadBillLines(type, rows);
+        }, true);
+    }
+    private static CoreTable LoadBillLines(Type type, CoreRow[] rows)
+    {
+        var result = new CoreTable();
+        result.LoadColumns(typeof(BillLine));
+        foreach (var row in rows)
+        {
+            var billrow = result.NewRow();
+            billrow.Set<BillLine, Guid>(x => x.OrderItem.ID, row.Get<PurchaseOrderItem, Guid>(x => x.ID));
+
+            var description = new List<string>();
+            if (row.Get<PurchaseOrderItem, Guid>(x => x.Product.ID) != Guid.Empty)
+                description.Add(string.Format("{0} : {1}", row.Get<PurchaseOrderItem, string>(x => x.Product.Code),
+                    row.Get<PurchaseOrderItem, string>(x => x.Product.Name)));
+            var Description = row.Get<PurchaseOrderItem, string>(x => x.Description);
+            if (!string.IsNullOrEmpty(Description))
+                description.Add(Description);
+            billrow.Set<BillLine, string>(x => x.Description, string.Join("\n", description));
+
+            billrow.Set<BillLine, Guid>(x => x.TaxCode.ID, row.Get<PurchaseOrderItem, Guid>(x => x.TaxCode.ID));
+            billrow.Set<BillLine, string>(x => x.TaxCode.Code, row.Get<PurchaseOrderItem, string>(x => x.TaxCode.Code));
+            billrow.Set<BillLine, string>(x => x.TaxCode.Description, row.Get<PurchaseOrderItem, string>(x => x.TaxCode.Description));
+            billrow.Set<BillLine, double>(x => x.TaxCode.Rate, row.Get<PurchaseOrderItem, double>(x => x.TaxCode.Rate));
+            billrow.Set<BillLine, double>(x => x.TaxRate, row.Get<PurchaseOrderItem, double>(x => x.TaxRate));
+            billrow.Set<BillLine, double>(x => x.ExTax, row.Get<PurchaseOrderItem, double>(x => x.ExTax));
+            billrow.Set<BillLine, double>(x => x.Tax, row.Get<PurchaseOrderItem, double>(x => x.Tax));
+            billrow.Set<BillLine, double>(x => x.IncTax, row.Get<PurchaseOrderItem, double>(x => x.IncTax));
+            result.Rows.Add(billrow);
+        }
 
-            var now = DateTime.Now;
-            using (new WaitCursor())
-            {
-                var items = LoadItems(rows);
-                foreach (var item in items)
-                    item.ReceivedDate = now;
-                new Client<PurchaseOrderItem>().Save(items, "Consignment Items Received");
-            }
+        return result;
+    }
 
-            return true;
+    private bool ReceiveItems(Button sender, CoreRow[] rows)
+    {
+        if (!rows.Any())
+        {
+            MessageBox.Show("Please select a row first");
+            return false;
         }
 
-        public static bool AssignLocation(Button btn, CoreRow[] rows)
+        var now = DateTime.Now;
+        using (new WaitCursor())
         {
-            if (!rows.Any())
-            {
-                MessageBox.Show("Please select at least one row to assign");
-                return false;
-            }
-
-            var menu = new ContextMenu();
-            menu.AddItem("Create New Location", null, () =>
-            {
-                var grid = new StockLocationGrid();
-                var location = new StockLocation();
-                if (grid.EditItems(new StockLocation[] { location }))
-                    AssignLocationToItems(location.ID, rows);
-            });
-            menu.AddItem("Choose Existing", null, () =>
-            {
-                var popup = new PopupList(typeof(StockLocation), Guid.Empty, new string[] { });
-                if (popup.ShowDialog() == true)
-                    AssignLocationToItems(popup.ID, rows);
-            });
-            menu.IsOpen = true;
-            return true;
+            var items = LoadItems(rows);
+            foreach (var item in items)
+                item.ReceivedDate = now;
+            new Client<PurchaseOrderItem>().Save(items, "Consignment Items Received");
         }
 
-        private static void AssignLocationToItems(Guid locationID, CoreRow[] rows)
+        return true;
+    }
+
+    public static bool AssignLocation(Button btn, CoreRow[] rows)
+    {
+        if (!rows.Any())
         {
-            List<PurchaseOrderItem> items = new List<PurchaseOrderItem>();
-            foreach (CoreRow row in rows)
-            {
-                var item = row.ToObject<PurchaseOrderItem>();
-                item.StockLocation.ID = locationID;
-                items.Add(item);
-            }
-            new Client<PurchaseOrderItem>()
-                .Save(items, "Added stock location from PurchaseOrderItem Grid");
+            MessageBox.Show("Please select at least one row to assign");
+            return false;
         }
 
-        /*private StockLocation[] QueryLocations()
+        var menu = new ContextMenu();
+        menu.AddItem("Create New Location", null, () =>
+        {
+            var grid = new StockLocationGrid();
+            var location = new StockLocation();
+            if (grid.EditItems(new StockLocation[] { location }))
+                AssignLocationToItems(location.ID, rows);
+        });
+        menu.AddItem("Choose Existing", null, () =>
         {
-            CoreTable table = new Client<StockLocation>().Query(new Filter<StockLocation>(x => x.Active).IsEqualTo(true), new Columns<StockLocation>(x => x.ID));
-            List<StockLocation> locations = new List<StockLocation>();
-            foreach (CoreRow row in table.Rows)
-                locations.Add(row.ToObject<StockLocation>());
+            var popup = new PopupList(typeof(StockLocation), Guid.Empty, new string[] { });
+            if (popup.ShowDialog() == true)
+                AssignLocationToItems(popup.ID, rows);
+        });
+        menu.IsOpen = true;
+        return true;
+    }
 
-            return locations.ToArray();
-        }*/
+    private static void AssignLocationToItems(Guid locationID, CoreRow[] rows)
+    {
+        var items = new List<PurchaseOrderItem>();
+        foreach (CoreRow row in rows)
+        {
+            var item = row.ToObject<PurchaseOrderItem>();
+            item.StockLocation.ID = locationID;
+            items.Add(item);
+        }
+        Client.Save(items, "Added stock location from PurchaseOrderItem Grid");
+    }
 
+    /*private StockLocation[] QueryLocations()
+    {
+        CoreTable table = new Client<StockLocation>().Query(new Filter<StockLocation>(x => x.Active).IsEqualTo(true), new Columns<StockLocation>(x => x.ID));
+        List<StockLocation> locations = new List<StockLocation>();
+        foreach (CoreRow row in table.Rows)
+            locations.Add(row.ToObject<StockLocation>());
+
+        return locations.ToArray();
+    }*/
 
-        protected override void SelectItems(CoreRow[]? rows)
-        {
-            if (createConsignment != null)
-            {
-                createConsignment.IsEnabled =
-                    rows != null
-                    && !rows.Any(r => Entity.IsEntityLinkValid<PurchaseOrderItem, ConsignmentLink>(x => x.Consignment, r))
-                    && !rows.Any(r => !r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty())
-                    && !ReadOnly && Security.CanEdit<Consignment>();
-            }
 
-            receive.IsEnabled =
+    protected override void SelectItems(CoreRow[]? rows)
+    {
+        if (createConsignment != null)
+        {
+            createConsignment.IsEnabled =
                 rows != null
                 && !rows.Any(r => Entity.IsEntityLinkValid<PurchaseOrderItem, ConsignmentLink>(x => x.Consignment, r))
-                && !rows.Any(r => r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty() == false)
-                && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
+                && !rows.Any(r => !r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty())
+                && !ReadOnly && Security.CanEdit<Consignment>();
+        }
 
-            bill.IsEnabled =
-                rows != null
-                && !rows.Any(r => r.IsEntityLinkValid<PurchaseOrderItem, BillLineLink>(x => x.BillLine))
-                && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
+        receive.IsEnabled =
+            rows != null
+            && !rows.Any(r => Entity.IsEntityLinkValid<PurchaseOrderItem, ConsignmentLink>(x => x.Consignment, r))
+            && !rows.Any(r => r.Get<PurchaseOrderItem, DateTime>(c => c.ReceivedDate).IsEmpty() == false)
+            && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
 
-            assignLocation.IsEnabled =
-                rows != null && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
+        bill.IsEnabled =
+            rows != null
+            && !rows.Any(r => r.IsEntityLinkValid<PurchaseOrderItem, BillLineLink>(x => x.BillLine))
+            && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
 
-            base.SelectItems(rows);
-        }
+        assignLocation.IsEnabled =
+            rows != null && !ReadOnly && Security.CanEdit<PurchaseOrderItem>();
 
-        protected override void CustomiseEditor(PurchaseOrderItem[] items, DynamicGridColumn column, BaseEditor editor)
-        {
-            base.CustomiseEditor(items, column, editor);
+        base.SelectItems(rows);
+    }
 
-            if(items.Any(x => x.ReceivedDate != DateTime.MinValue) && !new Column<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(column.ColumnName))
-            {
-                editor.Editable = editor.Editable.Combine(Editable.Disabled);
-            }
-        }
+    protected override void CustomiseEditor(PurchaseOrderItem[] items, DynamicGridColumn column, BaseEditor editor)
+    {
+        base.CustomiseEditor(items, column, editor);
 
-        protected override void OnAfterEditorValueChanged(DynamicEditorGrid? grid, PurchaseOrderItem[] items, AfterEditorValueChangedArgs args, Dictionary<string, object?> changes)
+        if(items.Any(x => x.ReceivedDate != DateTime.MinValue) && !new Column<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(column.ColumnName))
         {
-            base.OnAfterEditorValueChanged(grid, items, args, changes);
-            if (args.ColumnName.Equals("Product.ID") || args.ColumnName.Equals("Job.ID") || args.ColumnName.Equals("Dimensions") || args.ColumnName.StartsWith("Dimensions.") || args.ColumnName.Equals("Style.ID"))
-            {
-                PurchaseOrder.UpdateCosts(
-                    items, 
-                    Item.SupplierLink.ID,
-                    changes
-                );
-            }
+            editor.Editable = editor.Editable.Combine(Editable.Disabled);
         }
+    }
 
-        protected override Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, PurchaseOrderItem[] items, string name,
-            object value)
+    protected override void OnAfterEditorValueChanged(DynamicEditorGrid? grid, PurchaseOrderItem[] items, AfterEditorValueChangedArgs args, Dictionary<string, object?> changes)
+    {
+        base.OnAfterEditorValueChanged(grid, items, args, changes);
+        if (args.ColumnName.Equals("Product.ID") || args.ColumnName.Equals("Job.ID") || args.ColumnName.Equals("Dimensions") || args.ColumnName.StartsWith("Dimensions.") || args.ColumnName.Equals("Style.ID"))
         {
-            var results = base.EditorValueChanged(editor, items, name, value);
-            if (name.Equals("ProductLink.TaxCode.ID"))
-                DynamicGridUtils.UpdateEditorValue(items, "TaxCode.ID", (Guid)value, results);
-            return results;
+            PurchaseOrder.UpdateCosts(
+                items, 
+                Item.SupplierLink.ID,
+                changes
+            );
         }
     }
+
+    protected override Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, PurchaseOrderItem[] items, string name,
+        object value)
+    {
+        var results = base.EditorValueChanged(editor, items, name, value);
+        if (name.Equals("ProductLink.TaxCode.ID"))
+            DynamicGridUtils.UpdateEditorValue(items, "TaxCode.ID", (Guid)value, results);
+        return results;
+    }
 }

+ 6 - 0
prs.desktop/Panels/Tasks/KanbanSubscriberGrid.cs

@@ -15,6 +15,12 @@ namespace PRSDesktop
         protected override void Init()
         {
             base.Init();
+
+            HiddenColumns.Add(x => x.Manager);
+            HiddenColumns.Add(x => x.Assignee);
+            HiddenColumns.Add(x => x.Observer);
+            HiddenColumns.Add(x => x.Employee.ID);
+
             AddButton("Select Team", PRSDesktop.Resources.team.AsBitmapImage(), SelectTeamClick);
         }