Browse Source

Improvements to bill approval UI

Kenric Nugteren 9 months ago
parent
commit
307a7c25ff

+ 53 - 57
prs.desktop/Panels/Suppliers/Bills/SupplierBillEditLayout.xaml

@@ -32,64 +32,60 @@
             x:Name="Editors"
             Grid.Row="0"
             SelectionChanged="Editors_SelectionChanged"/>
-        
-        <sf:SfGridSplitter 
-            Grid.Row="0" 
-            Grid.Column="1"
-            Width="4"
-            VerticalAlignment="Stretch"
-            Background="Transparent"
-            ResizeBehavior="PreviousAndNext"
-            Template="{StaticResource VerticalSplitter}"
-            Visibility="{Binding CanApprove,Converter={StaticResource boolVisibilityConverter}}"/>
 
+        <dynamicGrid:DynamicTabControl x:Name="Dates" Grid.Row="0" Grid.Column="1" Margin="5,0,0,0">
+            
+        </dynamicGrid:DynamicTabControl>
 
-        <Border Grid.Row="0" Grid.Column="2" Margin="5,0,0,0"
-                BorderBrush="Gray" BorderThickness="0.75"
-                Background="White" Padding="7"
-                Visibility="{Binding CanApprove,Converter={StaticResource boolVisibilityConverter}}">
-            <Grid>
-                <Grid.RowDefinitions>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                    <RowDefinition Height="Auto"/>
-                </Grid.RowDefinitions>
-                <Label Content="PO Amount"
-                       Grid.Row="0" HorizontalAlignment="Center"/>
-                <sf:CurrencyTextBox x:Name="POMoneyBox"
-                                    Grid.Row="1" CurrencyDecimalDigits="2"
-                                    Height="25" Width="150"
-                                    Margin="0,0,0,5"
-                                    Background="WhiteSmoke"
-                                    VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
-                                    Value="{Binding POAmount}"/>
-                <Label Content="Bill Amount"
-                       Grid.Row="2" HorizontalAlignment="Center"/>
-                <sf:CurrencyTextBox x:Name="BillMoneyBox"
-                                    Grid.Row="3" CurrencyDecimalDigits="2"
-                                    Height="25" Width="150"
-                                    Margin="0,0,0,10"
-                                    VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
-                                    Value="{Binding BillAmount}">
-                    <sf:CurrencyTextBox.Style>
-                        <Style TargetType="sf:CurrencyTextBox">
-                            <Setter Property="Background" Value="LightGreen"/>
-                            <Style.Triggers>
-                                <DataTrigger Binding="{Binding BillLessThanPO}" Value="False">
-                                    <Setter Property="Background" Value="LightSalmon"/>
-                                </DataTrigger>
-                            </Style.Triggers>
-                        </Style>
-                    </sf:CurrencyTextBox.Style>
-                </sf:CurrencyTextBox>
-                <Button x:Name="ApproveButton" Grid.Row="4"
-                        Content="Approve"
-                        Width="150"
-                        Padding="5" Click="ApproveButton_Click"/>
-            </Grid>
-        </Border>
+        <dynamicGrid:DynamicTabControl x:Name="Approval" Grid.Row="0" Grid.Column="2" Margin="5,0,0,0"
+                                       Visibility="{Binding CanApprove,Converter={StaticResource boolVisibilityConverter}}">
+            <dynamicGrid:DynamicTabItem Header="Approval">
+                <Border BorderBrush="Gray" BorderThickness="0.75"
+                        Background="White" Padding="7" Margin="0,2,0,0">
+                    <Grid>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition Height="Auto"/>
+                        </Grid.RowDefinitions>
+                        <Label Content="PO Amount"
+                               Grid.Row="0" HorizontalAlignment="Center"/>
+                        <sf:CurrencyTextBox x:Name="POMoneyBox"
+                                            Grid.Row="1" CurrencyDecimalDigits="2"
+                                            Height="25" Width="150"
+                                            Margin="0,0,0,5"
+                                            Background="WhiteSmoke"
+                                            VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
+                                            Value="{Binding POAmount}"/>
+                        <Label Content="Bill Amount"
+                               Grid.Row="2" HorizontalAlignment="Center"/>
+                        <sf:CurrencyTextBox x:Name="BillMoneyBox"
+                                            Grid.Row="3" CurrencyDecimalDigits="2"
+                                            Height="25" Width="150"
+                                            Margin="0,0,0,10"
+                                            VerticalContentAlignment="Center" HorizontalContentAlignment="Center"
+                                            Value="{Binding BillAmount}">
+                            <sf:CurrencyTextBox.Style>
+                                <Style TargetType="sf:CurrencyTextBox">
+                                    <Setter Property="Background" Value="LightGreen"/>
+                                    <Style.Triggers>
+                                        <DataTrigger Binding="{Binding BillLessThanPO}" Value="False">
+                                            <Setter Property="Background" Value="LightSalmon"/>
+                                        </DataTrigger>
+                                    </Style.Triggers>
+                                </Style>
+                            </sf:CurrencyTextBox.Style>
+                        </sf:CurrencyTextBox>
+                        <Button x:Name="ApproveButton" Grid.Row="4"
+                                Content="Approve"
+                                Width="150"
+                                Padding="5" Click="ApproveButton_Click"/>
+                    </Grid>
+                </Border>
+            </dynamicGrid:DynamicTabItem>
+        </dynamicGrid:DynamicTabControl>
         
         <sf:SfGridSplitter 
             Grid.Row="1" 
@@ -102,7 +98,7 @@
             PreviewStyle="{StaticResource HorizontalSplitterPreview}"/>
         
         <dynamicGrid:DynamicTabControl x:Name="OtherPages"
-                                       Grid.Row="2" Grid.ColumnSpan="3"
+                                       Grid.Row="2" Grid.ColumnSpan="4"
                                        SelectionChanged="Editors_SelectionChanged"
                                        TabStripPlacement="Bottom"/>
         

+ 18 - 5
prs.desktop/Panels/Suppliers/Bills/SupplierBillEditLayout.xaml.cs

@@ -36,9 +36,10 @@ public partial class SupplierBillEditLayout : DynamicEditorGridLayout, INotifyPr
     private double _totalWidth;
     public override double TotalWidth => _totalWidth;
 
+    private double _datesHeight;
     private double _editorHeight;
     private double _pageHeight;
-    public override double TotalHeight => _editorHeight + _pageHeight;
+    public override double TotalHeight => Math.Max(_datesHeight, _editorHeight) + _pageHeight;
 
     private double _poAmount;
     public double POAmount
@@ -99,6 +100,9 @@ public partial class SupplierBillEditLayout : DynamicEditorGridLayout, INotifyPr
     {
         Editors.Items.Clear();
         OtherPages.Items.Clear();
+
+        Dates.Items.Clear();
+
         foreach (var page in pages.OrderBy(x => x.PageType).ThenBy(x => x.Order).ThenBy(x => x.Caption()))
         {
             var tab = new DynamicTabItem();
@@ -111,18 +115,27 @@ public partial class SupplierBillEditLayout : DynamicEditorGridLayout, INotifyPr
 
             if(page is DynamicEditorGrid.DynamicEditPage)
             {
-                Editors.Items.Add(tab);
-                _editorHeight = Math.Max(_editorHeight, minSize.Height);
-                _totalWidth = Math.Max(_totalWidth, minSize.Width);
+                if (page.Caption() == "Dates")
+                {
+                    Dates.Items.Add(tab);
+                    _datesHeight = Math.Max(_editorHeight, minSize.Height);
+                }
+                else
+                {
+                    Editors.Items.Add(tab);
+                    _editorHeight = Math.Max(_editorHeight, minSize.Height);
+                    _totalWidth = Math.Max(_totalWidth, minSize.Width);
+                }
             }
             else
             {
                 OtherPages.Items.Add(tab);
                 _pageHeight = Math.Max(_pageHeight, minSize.Height);
             }
-
         }
 
+        Dates.Visibility = Dates.Items.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
+        Dates.SelectedIndex = 0;
         Editors.SelectedIndex = 0;
         OtherPages.SelectedIndex = 0;
     }

+ 1 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBillPanel.xaml

@@ -54,6 +54,7 @@
                 OnOK="Bill_OnOnOK" 
                 OnCancel="Bill_OnOnCancel" 
                 OnChanged="Bill_OnOnChanged"
+                OnFormCustomiseEditor="Bill_OnFormCustomiseEditor"
                 HighlightButtons="True"
                 HideButtons="True"/>
         </dynamicGrid:DynamicSplitPanel.Detail>

+ 19 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBillPanel.xaml.cs

@@ -17,6 +17,7 @@ using System.Collections.ObjectModel;
 using System.Windows.Media;
 using System.Runtime.CompilerServices;
 using SuperSocket.ClientEngine;
+using com.sun.tools.@internal.jxc.gen.config;
 
 namespace PRSDesktop;
 
@@ -530,6 +531,24 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
     {
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
     }
+
+    private Column<Bill> ApprovalSetID = new(x => x.ApprovalSet.ID);
+
+    private void Bill_OnFormCustomiseEditor(IDynamicEditorForm sender, object items, DynamicGridColumn column, BaseEditor editor)
+    {
+        if(column.ColumnName == "Approved")
+        {
+            editor.Editable = Editable.Hidden;
+        }
+        if((editor is DateEditor || editor is TimestampEditor) && !column.ColumnName.Contains("."))
+        {
+            if(editor.Page == "Additional")
+            {
+                editor.EditorSequence += 100;
+            }
+            editor.Page = "Dates";
+        }
+    }
 }
 
 public class BillDocumentViewList : DocumentViewList<BillDocument>

+ 183 - 169
prs.desktop/Panels/Suppliers/Bills/SupplierBills.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using Comal.Classes;
@@ -11,213 +12,226 @@ using InABox.DynamicGrid;
 using InABox.WPF;
 using NPOI.Util;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class SupplierBills : DynamicDataGrid<Bill>
 {
-    public class SupplierBills : DynamicDataGrid<Bill>
-    {
-        private static readonly BitmapImage? tick = PRSDesktop.Resources.tick.AsBitmapImage();
-        private static readonly BitmapImage? data = PRSDesktop.Resources.pencil.AsBitmapImage();
-        private static readonly BitmapImage? check = PRSDesktop.Resources.checklist.AsBitmapImage();
+    private static readonly BitmapImage? tick = PRSDesktop.Resources.tick.AsBitmapImage();
+    private static readonly BitmapImage? data = PRSDesktop.Resources.pencil.AsBitmapImage();
+    private static readonly BitmapImage? check = PRSDesktop.Resources.checklist.AsBitmapImage();
 
-        private SupplierBillPanelProperties _settings = null;
+    private SupplierBillPanelProperties _settings = null;
+    
+    public SupplierBills()
+    {
         
-        public SupplierBills()
-        {
-            
-            _settings = new GlobalConfiguration<SupplierBillPanelProperties>().Load();
+        _settings = new GlobalConfiguration<SupplierBillPanelProperties>().Load();
 
-            HiddenColumns.Add(x => x.Approved);
-            HiddenColumns.Add(x => x.DataEntered);
-            HiddenColumns.Add(x => x.Checked);
-            HiddenColumns.Add(x => x.ApprovalSet.ID);
+        HiddenColumns.Add(x => x.Approved);
+        HiddenColumns.Add(x => x.DataEntered);
+        HiddenColumns.Add(x => x.Checked);
+        HiddenColumns.Add(x => x.ApprovalSet.ID);
 
-            ActionColumns.Add(new DynamicImageColumn(DataEntered_Image, null) { ToolTip = DataEntered_ToolTip });
-            ActionColumns.Add(new DynamicImageColumn(Checked_Image, null) { ToolTip = Checked_ToolTip });
-            ActionColumns.Add(new DynamicImageColumn(Approved_Image, null) { ToolTip = Approved_ToolTip });
+        ActionColumns.Add(new DynamicImageColumn(DataEntered_Image, null) { ToolTip = DataEntered_ToolTip });
+        ActionColumns.Add(new DynamicImageColumn(Checked_Image, null) { ToolTip = Checked_ToolTip });
+        ActionColumns.Add(new DynamicImageColumn(Approved_Image, null) { ToolTip = Approved_ToolTip });
 
-            PostUtils.AddPostColumn(this);
+        PostUtils.AddPostColumn(this);
 
-            ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
-        }
+        ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
+    }
 
-        private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
-        {
-            if (row is null) return;
+    private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
+    {
+        if (row is null) return;
 
-            var menu = column.GetMenu();
-            menu.AddItem("Change Approval Set", null, row, SetBillApprovalSet);
-        }
+        var menu = column.GetMenu();
+        var approvalSetItem = menu.AddItem("Approval Set", null, null);
+        approvalSetItem.AddItem("Loading...", null, null, enabled: false);
 
-        public override DynamicEditorPages LoadEditorPages(Bill item)
+        var id = row.Get<Bill, Guid>(x => x.ID);
+        var selectedApproval = row.Get<Bill, Guid>(x => x.ApprovalSet.ID);
+        Task.Run(() =>
         {
-            // Need to re-order the pages, since we don't want approvals before lines or documents.
+            return Client.Query<BillApprovalSet>(
+                null,
+                Columns.None<BillApprovalSet>().Add(x => x.ID).Add(x => x.Code).Add(x => x.Description))
+                .ToArray<BillApprovalSet>();
+        }).ContinueWith(approvals =>
+        {
+            approvalSetItem.Items.Clear();
+            foreach(var approval in approvals.Result)
+            {
+                var item = approvalSetItem.AddItem($"{approval.Code}: {approval.Description}", null, (row, approval), ApprovalSet_Click);
+                item.IsChecked = approval.ID == selectedApproval;
+            }
+        }, TaskScheduler.FromCurrentSynchronizationContext());
+    }
 
-            var pages = base.LoadEditorPages(item);
+    private void ApprovalSet_Click((CoreRow row, BillApprovalSet approvalSet) item)
+    {
+        var bill = item.row.ToObject<Bill>();
+        if (bill.ApprovalSet.ID == item.approvalSet.ID) return;
+
+        BillApprovalSetEmployee[] oldEmployees;
+        if(bill.ApprovalSet.ID != Guid.Empty)
+        {
+            oldEmployees = Client.Query(
+                new Filter<BillApprovalSetEmployee>(x => x.ApprovalSet.ID).IsEqualTo(bill.ApprovalSet.ID),
+                Columns.None<BillApprovalSetEmployee>().Add(x => x.Employee.ID),
+                new SortOrder<BillApprovalSetEmployee>(x => x.Sequence))
+                .ToArray<BillApprovalSetEmployee>();
+        }
+        else
+        {
+            oldEmployees = [];
+        }
 
-            if(pages.TryGetPage<SupplierBillLineGrid>(out var billLineGrid))
+        var newEmployees = Client.Query(
+            new Filter<BillApprovalSetEmployee>(x => x.ApprovalSet.ID).IsEqualTo(item.approvalSet.ID),
+            Columns.None<BillApprovalSetEmployee>().Add(x => x.Employee.ID),
+            new SortOrder<BillApprovalSetEmployee>(x => x.Sequence))
+            .ToArray<BillApprovalSetEmployee>();
+
+        var approvals = Client.Query(
+            new Filter<BillApproval>(x => x.Bill.ID).IsEqualTo(bill.ID),
+            Columns.None<BillApproval>()
+                .Add(x => x.ID)
+                .Add(x => x.Approved)
+                .Add(x => x.Employee.ID)
+                .Add(x => x.Sequence),
+            new SortOrder<BillApproval>(x => x.Sequence))
+            .ToArray<BillApproval>();
+
+        var toDelete = new List<BillApproval>();
+        var toSave = new List<BillApproval>();
+        foreach(var employee in newEmployees)
+        {
+            if(!approvals.Any(x => x.Employee.ID == employee.Employee.ID))
             {
-                billLineGrid.Order = 0;
+                var newApproval = new BillApproval();
+                newApproval.Employee.CopyFrom(employee.Employee);
+                newApproval.Bill.CopyFrom(bill);
+                toSave.Add(newApproval);
             }
-            if(pages.TryGetPage<DynamicDocumentGrid<BillDocument, Bill, BillLink>>(out var docGrid))
+        }
+        foreach(var approval in approvals)
+        {
+            if(approval.Approved == DateTime.MinValue
+                && oldEmployees.Any(x => x.Employee.ID == approval.Employee.ID)
+                && !newEmployees.Any(x => x.Employee.ID == approval.Employee.ID))
             {
-                docGrid.Order = 1;
+                toDelete.Add(approval);
             }
-            if(pages.TryGetPage<DynamicOneToManyGrid<Bill, BillApproval>>(out var approvalGrid))
+            else
             {
-                approvalGrid.Order = 2;
+                toSave.Add(approval);
             }
-            
-            return pages;
         }
-
-        private void SetBillApprovalSet(CoreRow row)
+        foreach(var (i, approval) in toSave.WithIndex())
         {
-            var bill = row.ToObject<Bill>();
-
-            if(MultiSelectDialog<BillApprovalSet>.SelectItem(
-                out var approvalSet,
-                filter: new Filter<BillApprovalSet>(x => x.ID).IsNotEqualTo(bill.ApprovalSet.ID),
-                columns: Columns.None<BillApprovalSet>().Add(x => x.ID),
-                title: "Select Approval Set:"))
-            {
-                BillApprovalSetEmployee[] oldEmployees;
-                if(bill.ApprovalSet.ID != Guid.Empty)
-                {
-                    oldEmployees = Client.Query(
-                        new Filter<BillApprovalSetEmployee>(x => x.ApprovalSet.ID).IsEqualTo(bill.ApprovalSet.ID),
-                        Columns.None<BillApprovalSetEmployee>().Add(x => x.Employee.ID),
-                        new SortOrder<BillApprovalSetEmployee>(x => x.Sequence))
-                        .ToArray<BillApprovalSetEmployee>();
-                }
-                else
-                {
-                    oldEmployees = [];
-                }
-
-                var newEmployees = Client.Query(
-                    new Filter<BillApprovalSetEmployee>(x => x.ApprovalSet.ID).IsEqualTo(approvalSet.ID),
-                    Columns.None<BillApprovalSetEmployee>().Add(x => x.Employee.ID),
-                    new SortOrder<BillApprovalSetEmployee>(x => x.Sequence))
-                    .ToArray<BillApprovalSetEmployee>();
-
-                var approvals = Client.Query(
-                    new Filter<BillApproval>(x => x.Bill.ID).IsEqualTo(bill.ID),
-                    Columns.None<BillApproval>()
-                        .Add(x => x.ID)
-                        .Add(x => x.Approved)
-                        .Add(x => x.Employee.ID)
-                        .Add(x => x.Sequence),
-                    new SortOrder<BillApproval>(x => x.Sequence))
-                    .ToArray<BillApproval>();
-
-                var toDelete = new List<BillApproval>();
-                var toSave = new List<BillApproval>();
-                foreach(var employee in newEmployees)
-                {
-                    if(!approvals.Any(x => x.Employee.ID == employee.Employee.ID))
-                    {
-                        var newApproval = new BillApproval();
-                        newApproval.Employee.CopyFrom(employee.Employee);
-                        newApproval.Bill.CopyFrom(bill);
-                        toSave.Add(newApproval);
-                    }
-                }
-                foreach(var approval in approvals)
-                {
-                    if(approval.Approved == DateTime.MinValue
-                        && oldEmployees.Any(x => x.Employee.ID == approval.Employee.ID)
-                        && !newEmployees.Any(x => x.Employee.ID == approval.Employee.ID))
-                    {
-                        toDelete.Add(approval);
-                    }
-                    else
-                    {
-                        toSave.Add(approval);
-                    }
-                }
-                foreach(var (i, approval) in toSave.WithIndex())
-                {
-                    approval.Sequence = i;
-                }
-                Client.Delete(toDelete, "Deleted by changing approval set.");
-                Client.Save(toSave, "Updated when changing approval set.");
-
-                bill.ApprovalSet.CopyFrom(approvalSet);
-                SaveItem(bill);
-                Refresh(false, true);
-            }
+            approval.Sequence = i;
         }
+        Client.Delete(toDelete, "Deleted by changing approval set.");
+        Client.Save(toSave, "Updated when changing approval set.");
+
+        bill.ApprovalSet.CopyFrom(item.approvalSet);
+        SaveItem(bill);
+        Refresh(false, true);
+    }
+
+
+    public override DynamicEditorPages LoadEditorPages(Bill item)
+    {
+        // Need to re-order the pages, since we don't want approvals before lines or documents.
+
+        var pages = base.LoadEditorPages(item);
 
-        private FrameworkElement? Approved_ToolTip(DynamicActionColumn column, CoreRow? row)
+        if(pages.TryGetPage<SupplierBillLineGrid>(out var billLineGrid))
         {
-            return row is null ? column.TextToolTip("Has this bill been approved?")
-                : row.Get<Bill, DateTime>(x => x.Approved).IsEmpty() ? column.TextToolTip("Not yet approved")
-                : column.TextToolTip("Approved");
+            billLineGrid.Order = 0;
         }
-
-        private FrameworkElement? Checked_ToolTip(DynamicActionColumn column, CoreRow? row)
+        if(pages.TryGetPage<DynamicDocumentGrid<BillDocument, Bill, BillLink>>(out var docGrid))
         {
-            return row is null ? column.TextToolTip("Has this bill been checked?")
-                : row.Get<Bill, DateTime>(x => x.Approved).IsEmpty() ? column.TextToolTip("Not yet checked")
-                : column.TextToolTip("Checked");
+            docGrid.Order = 1;
         }
-
-        private FrameworkElement? DataEntered_ToolTip(DynamicActionColumn column, CoreRow? row)
+        if(pages.TryGetPage<DynamicOneToManyGrid<Bill, BillApproval>>(out var approvalGrid))
         {
-            return row is null ? column.TextToolTip("Has this bill been entered via Data Entry?")
-                : row.Get<Bill, DateTime>(x => x.Approved).IsEmpty() ? column.TextToolTip("Data not entered.")
-                : column.TextToolTip("Data Entered");
+            approvalGrid.Order = 2;
         }
+        
+        return pages;
+    }
 
-        protected override void DoReconfigure(DynamicGridOptions options)
-        {
-            base.DoReconfigure(options);
+    private FrameworkElement? Approved_ToolTip(DynamicActionColumn column, CoreRow? row)
+    {
+        return row is null ? column.TextToolTip("Has this bill been approved?")
+            : row.Get<Bill, DateTime>(x => x.Approved).IsEmpty() ? column.TextToolTip("Not yet approved")
+            : column.TextToolTip("Approved");
+    }
 
-            options.FilterRows = true;
-            options.SelectColumns = true;
-            options.MultiSelect = true;
-            options.RecordCount = true;
-            options.ShowHelp = true; 
-        }
+    private FrameworkElement? Checked_ToolTip(DynamicActionColumn column, CoreRow? row)
+    {
+        return row is null ? column.TextToolTip("Has this bill been checked?")
+            : row.Get<Bill, DateTime>(x => x.Approved).IsEmpty() ? column.TextToolTip("Not yet checked")
+            : column.TextToolTip("Checked");
+    }
 
-        private BitmapImage? DataEntered_Image(CoreRow? row)
-        {
-            return (row != null) && row.Get<Bill, DateTime>(x => x.DataEntered).IsEmpty()
-                ? null
-                : data;
-        }
+    private FrameworkElement? DataEntered_ToolTip(DynamicActionColumn column, CoreRow? row)
+    {
+        return row is null ? column.TextToolTip("Has this bill been entered via Data Entry?")
+            : row.Get<Bill, DateTime>(x => x.Approved).IsEmpty() ? column.TextToolTip("Data not entered.")
+            : column.TextToolTip("Data Entered");
+    }
 
-        private BitmapImage? Checked_Image(CoreRow? row)
-        {
-            return (row != null) && row.Get<Bill, DateTime>(x => x.Checked).IsEmpty()
-                ? null
-                : check;
-        }
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
 
-        private BitmapImage? Approved_Image(CoreRow? row)
-        {
-            return (row != null) && row.Get<Bill, DateTime>(x => x.Approved).IsEmpty()
-                ? null
-                : tick;
-        }
+        options.FilterRows = true;
+        options.SelectColumns = true;
+        options.MultiSelect = true;
+        options.RecordCount = true;
+        options.ShowHelp = true; 
+    }
+
+    private BitmapImage? DataEntered_Image(CoreRow? row)
+    {
+        return (row != null) && row.Get<Bill, DateTime>(x => x.DataEntered).IsEmpty()
+            ? null
+            : data;
+    }
+
+    private BitmapImage? Checked_Image(CoreRow? row)
+    {
+        return (row != null) && row.Get<Bill, DateTime>(x => x.Checked).IsEmpty()
+            ? null
+            : check;
+    }
+
+    private BitmapImage? Approved_Image(CoreRow? row)
+    {
+        return (row != null) && row.Get<Bill, DateTime>(x => x.Approved).IsEmpty()
+            ? null
+            : tick;
+    }
+
+    public Bill[] LoadBills(CoreRow[] rows)
+    {
+        return LoadItems(rows);
+    }
+
+    protected override void DoValidate(Bill[] items, List<string> errors)
+    {
+        base.DoValidate(items, errors);
 
-        public Bill[] LoadBills(CoreRow[] rows)
+        if(items.Any(x => x.Number.IsNullOrWhiteSpace()) && !_settings.AllowBlankBillNumbers)
         {
-            return LoadItems(rows);
+            errors.Add($"[{nameof(Bill.Number)}] may not be blank!");
         }
-
-        protected override void DoValidate(Bill[] items, List<string> errors)
+        if(items.Any(x => x.SupplierLink.ID == Guid.Empty))
         {
-            base.DoValidate(items, errors);
-
-            if(items.Any(x => x.Number.IsNullOrWhiteSpace()) && !_settings.AllowBlankBillNumbers)
-            {
-                errors.Add($"[{nameof(Bill.Number)}] may not be blank!");
-            }
-            if(items.Any(x => x.SupplierLink.ID == Guid.Empty))
-            {
-                errors.Add($"[{nameof(Bill.SupplierLink)}] may not be blank!");
-            }
+            errors.Add($"[{nameof(Bill.SupplierLink)}] may not be blank!");
         }
     }
 }