Jelajahi Sumber

Converted DynamicGridFilters to CoreFilterDefinitions
Improved Database Update Script system

frogsoftware 2 tahun lalu
induk
melakukan
4f51f01931

+ 5 - 5
prs.desktop/Components/JobSelector/JobSelector.xaml.cs

@@ -92,7 +92,7 @@ namespace PRSDesktop
         }
         
         private CoreTable _jobs;
-        private DynamicGridFilters _filters;
+        private CoreFilterDefinitions _filters;
         
         public T[] GetJobData<T>(Func<CoreRow, T> transform)
         {
@@ -115,14 +115,14 @@ namespace PRSDesktop
         {
             using (new EventSuppressor(Suppress.This))
             {
-                _filters = new GlobalConfiguration<DynamicGridFilters>("Job").Load();
+                _filters = new GlobalConfiguration<CoreFilterDefinitions>("Job").Load();
                 JobFilter.ItemsSource = _filters;
             }
         }
 
         private String GetSelectedJobFilter()
         {
-            return (JobFilter.SelectedValue is DynamicGridFilter filter) ? filter.Name : "";
+            return (JobFilter.SelectedValue is CoreFilterDefinition filter) ? filter.Name : "";
         }
 
         private Guid[] GetSelectedJobs()
@@ -207,11 +207,11 @@ namespace PRSDesktop
 
         private void JobFilterSelector_OnClick(object sender, RoutedEventArgs e)
         {
-            var selfilter = (JobFilter.SelectedValue as DynamicGridFilter)?.Name ?? "";
+            var selfilter = (JobFilter.SelectedValue as CoreFilterDefinition)?.Name ?? "";
             var window = new DynamicGridFilterEditor(_filters, typeof(Job));
             if (window.ShowDialog() == true)
             {
-                new GlobalConfiguration<DynamicGridFilters>("Job").Save(_filters);
+                new GlobalConfiguration<CoreFilterDefinitions>("Job").Save(_filters);
                 using (new EventSuppressor(Suppress.This))
                     JobFilter.SelectedValue = _filters.FirstOrDefault(x => String.Equals(x.Name, selfilter));
             }

+ 2 - 2
prs.desktop/Dashboards/Common/DigitalFormsDashboard.xaml.cs

@@ -590,8 +590,8 @@ namespace PRSDesktop
         private void ManageFilters_Click()
         {
             var filters = Properties.Filters.GetValueOrDefault(ParentType!.Name) ?? new List<DFFilter>();
-            var gridFilters = new DynamicGridFilters();
-            gridFilters.AddRange(filters.Select(x => new DynamicGridFilter { Name = x.Name, Filter = x.Filter }));
+            var gridFilters = new CoreFilterDefinitions();
+            gridFilters.AddRange(filters.Select(x => new CoreFilterDefinition { Name = x.Name, Filter = x.Filter }));
 
             var grid = new DynamicGridFilterEditor(gridFilters, FormType!);
             if (grid.ShowDialog() == true)

+ 4 - 4
prs.desktop/Panels/EmployeePlanner/EmployeeResourcePlanner.xaml.cs

@@ -61,7 +61,7 @@ namespace PRSDesktop
         private JobModel[] _jobs = new JobModel[] { };
         private ActivityModel[] _activities = new ActivityModel[] { };
 
-        private DynamicGridFilters _jobfilters = new DynamicGridFilters();
+        private CoreFilterDefinitions _jobfilters = new CoreFilterDefinitions();
         
         public event LoadSettings<EmployeeResourcePlannerProperties> LoadSettings;
         public event SaveSettings<EmployeeResourcePlannerProperties> SaveSettings;
@@ -69,7 +69,7 @@ namespace PRSDesktop
         private void DoLoadSettings()
         {
             Properties = LoadSettings?.Invoke(this) ?? new EmployeeResourcePlannerProperties();
-            _jobfilters = new GlobalConfiguration<DynamicGridFilters>("Job").Load();
+            _jobfilters = new GlobalConfiguration<CoreFilterDefinitions>("Job").Load();
 
         }
         
@@ -598,7 +598,7 @@ namespace PRSDesktop
         {
             if (EventSuppressor.IsSet(Suppress.This))
                 return;
-            var sel = JobFilter.SelectedValue as DynamicGridFilter;
+            var sel = JobFilter.SelectedValue as CoreFilterDefinition;
             Properties.JobFilter = sel?.Name ?? "";
             using (new WaitCursor())
             {
@@ -616,7 +616,7 @@ namespace PRSDesktop
             var window = new DynamicGridFilterEditor(_jobfilters, typeof(Job));
             if (window.ShowDialog() == true)
             {
-                new GlobalConfiguration<DynamicGridFilters>("Job").Save(_jobfilters);
+                new GlobalConfiguration<CoreFilterDefinitions>("Job").Save(_jobfilters);
                 JobFilter.SelectedValue = _jobfilters.FirstOrDefault(x => String.Equals(x.Name, Properties.JobFilter));
             }
         }

+ 4 - 0
prs.desktop/Panels/Invoices/InvoiceListGrid.cs

@@ -66,12 +66,16 @@ namespace PRSDesktop
         }
 
         public Guid JobID { get; set; }
+        
+        public bool IncludePaidInvoices { get; set; }
 
         protected override void Reload(Filters<Invoice> criteria, Columns<Invoice> columns, ref SortOrder<Invoice> sort,
             Action<CoreTable, Exception> action)
         {
             if (JobID != Guid.Empty)
                 criteria.Add(new Filter<Invoice>(x => x.JobLink.ID).IsEqualTo(JobID));
+            if (!IncludePaidInvoices)
+                criteria.Add(new Filter<Invoice>(x=>x.Balance).IsNotEqualTo(FilterConstant.Zero));
             base.Reload(criteria, columns, ref sort, action);
         }
 

+ 19 - 2
prs.desktop/Panels/Invoices/InvoicePanel.xaml

@@ -16,8 +16,25 @@
         MasterCaption="Invoice List"
         DetailHeight="300">
         <dynamicGrid:DynamicSplitPanel.Header>
-            <Border BorderThickness="0.75" BorderBrush="Gray" Background="WhiteSmoke">
-                <Label Content="Invoice List" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
+            <Border BorderThickness="0.75" BorderBrush="Gray" Background="WhiteSmoke" Padding="5,0">
+                <DockPanel>
+                    <Label 
+                        Content="Include Paid?" 
+                        DockPanel.Dock="Right"
+                        HorizontalContentAlignment="Center" 
+                        VerticalContentAlignment="Center"/>
+                    <CheckBox
+                        x:Name="_includePaid"
+                        DockPanel.Dock="Right"
+                        VerticalContentAlignment="Center"
+                        Checked="_includePaid_OnChecked"
+                        Unchecked="_includePaid_OnChecked"/>
+                    <Label 
+                        Content="Invoice List" 
+                        DockPanel.Dock="Left"
+                        HorizontalContentAlignment="Center" 
+                        VerticalContentAlignment="Center"/>
+                </DockPanel>
             </Border>
         </dynamicGrid:DynamicSplitPanel.Header>
         <dynamicGrid:DynamicSplitPanel.Master>

+ 22 - 0
prs.desktop/Panels/Invoices/InvoicePanel.xaml.cs

@@ -15,15 +15,28 @@ using InABox.DynamicGrid;
 
 namespace PRSDesktop
 {
+
+    public class InvoicePanelSettings : IUserConfigurationSettings
+    {
+        public bool IncludePaidInvoices { get; set; }
+    }
+    
     /// <summary>
     ///     Interaction logic for InvoiceGrid.xaml
     /// </summary>
     public partial class InvoicePanel : UserControl, IPanel<Invoice>, IJobControl
     {
+
+        private InvoicePanelSettings _settings;
+        
         public InvoicePanel()
         {
+            _settings = new UserConfiguration<InvoicePanelSettings>().Load();
             InitializeComponent();
+            Invoices.IncludePaidInvoices = _settings.IncludePaidInvoices;
+            _includePaid.IsChecked = _settings.IncludePaidInvoices;
             Invoices.OnSelectItem += Invoices_OnSelectItem;
+            
         }
 
         public Guid ParentID
@@ -111,5 +124,14 @@ namespace PRSDesktop
             Lines.InvoiceID = invoiceid;
             Lines.Refresh(false, true);
         }
+        
+        private void _includePaid_OnChecked(object sender, RoutedEventArgs e)
+        {
+            _settings.IncludePaidInvoices = _includePaid.IsChecked == true;
+            new UserConfiguration<InvoicePanelSettings>().Save(_settings);
+            Invoices.IncludePaidInvoices = _includePaid.IsChecked == true;
+            if (IsReady)
+                Refresh();
+        }
     }
 }

+ 3 - 3
prs.desktop/Panels/Products/Reservation Management/JobRequisitionReviewGrid.cs

@@ -15,11 +15,11 @@ namespace PRSDesktop
 {
     public class JobRequisitionReviewUserSettings : IUserConfigurationSettings
     {
-        public DynamicGridFilter Filter { get; set; }
+        public CoreFilterDefinition Filter { get; set; }
 
         public JobRequisitionReviewUserSettings()
         {
-            Filter = new DynamicGridFilter();
+            Filter = new CoreFilterDefinition();
         }
     }
 
@@ -115,7 +115,7 @@ namespace PRSDesktop
         }
 
 
-        private bool GridOnFilterSelected(DynamicGridFilter filter)
+        private bool GridOnFilterSelected(CoreFilterDefinition filter)
         {
             new UserConfiguration<JobRequisitionReviewUserSettings>().Save(new JobRequisitionReviewUserSettings { Filter = filter });
             OnGridRefresh?.Invoke();

+ 35 - 0
prs.shared/Database Update Scripts/DatabaseUpdateScripts.cs

@@ -0,0 +1,35 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+using System;
+using System.Collections.Generic;
+using System.Configuration.Provider;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+using Syncfusion.Windows.Tools.Controls;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using FastReport.Utils;
+
+namespace PRS.Shared
+{
+    public static class DatabaseUpdateScripts
+    {
+        public static void RegisterScripts()
+        {
+            DataUpdater.RegisterUpdateScript<Update_6_31>();
+            DataUpdater.RegisterUpdateScript<Update_6_37>();
+            DataUpdater.RegisterUpdateScript<Update_6_38>();
+            DataUpdater.RegisterUpdateScript<Update_6_39>();
+            DataUpdater.RegisterUpdateScript<Update_6_43>();
+            DataUpdater.RegisterUpdateScript<Update_7_00>();
+            DataUpdater.RegisterUpdateScript<Update_7_06>();
+            DataUpdater.RegisterUpdateScript<Update_7_14>();
+            DataUpdater.RegisterUpdateScript<Update_7_19>();
+            DataUpdater.RegisterUpdateScript<Update_7_21>();
+        }
+    }
+}

+ 62 - 0
prs.shared/Database Update Scripts/Update_6_31.cs

@@ -0,0 +1,62 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_6_31 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (6, 31);
+        
+    private static Dictionary<string, Tuple<string, string>> _6_31_module_map = new()
+    {
+        { "Assignments", new("Assignments", "Assignments") },
+        { "Daily Report", new("Daily Report", "Assignments") },
+        { "Delivered On Site", new("Delivered On Site", "Delivery Items") },
+        { "Deliveries", new("Deliveries", "Deliveries") },
+        { "Digital Forms", new("Digital Forms", "DigitalForm") },
+        { "Employee List", new("Employees", "Employee") },
+        { "Equipment List", new("Equipment", "Equipment") },
+        { "Factory Floor", new("Factory", "Manufacturing Packets") },
+        { "Incoming Consignments", new("Consignments", "Consignment") },
+        { "Manufacturing Status", new("Manufacturing Packets", "Manufacturing Packets") },
+        { "Product List", new("Products", "Products") },
+        { "Projects", new("Job Details", "Job Details") },
+        { "Purchase Orders", new("Purchase Orders", "PurchaseOrder") },
+        { "Quotes", new("Quotes", "Quotes") },
+        { "Rack List", new("Shipping", "Shipments") },
+        { "Site Requisitions", new("Requisitions", "Requisition") },
+        { "Staff TimeSheets", new("Timesheets", "TimeSheet") },
+        { "Stock Locations", new("Stock Locations", "StockLocation") },
+        { "Stock Movements", new("Stock Movements", "StockMovement") },
+        { "Task List", new("Tasks By Status", "Kanban") },
+    };
+    
+    public override bool Update()
+    {
+        var modules = DbFactory.Provider.Query(new Filter<CustomModule>().All())
+            .Rows.Select(x => x.ToObject<CustomModule>()).ToList();
+
+        foreach(var module in modules)
+        {
+            if (!string.IsNullOrWhiteSpace(module.Section))
+            {
+                if (_6_31_module_map.TryGetValue(module.Section, out var map))
+                {
+                    module.Section = map.Item1;
+                    module.DataModel = map.Item2;
+                    module.AllRecords = true;
+                }
+                else
+                {
+                    Logger.Send(LogType.Error, "", $"Custom Module '{module.Name}' has section name '{module.Section}' and will no longer be visible!");
+                }
+            }
+        }
+        DbFactory.Provider.Save(modules);
+
+        return true;
+    }
+    
+}

+ 18 - 0
prs.shared/Database Update Scripts/Update_6_37.cs

@@ -0,0 +1,18 @@
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_6_37 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (6, 37);
+        
+    public override bool Update()
+    {
+        Logger.Send(LogType.Information, "", "Recreating views");
+        DbFactory.Provider.ForceRecreateViews();
+        return true;
+    }
+
+}

+ 30 - 0
prs.shared/Database Update Scripts/Update_6_38.cs

@@ -0,0 +1,30 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_6_38 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (6, 38);
+        
+    public override bool Update()
+    {
+        Logger.Send(LogType.Information, "", "Converting Job Requisition Dates to Due Dates");
+        List<JobRequisition> updates = new List<JobRequisition>();
+        var columns = new Columns<JobRequisition>(x => x.ID);
+        columns.Add("Date");
+        CoreTable requis = DbFactory.Provider.Query<JobRequisition>(null, columns);
+        foreach (var row in requis.Rows)
+        {
+            var requi = row.ToObject<JobRequisition>();
+            requi.Approved = row.Get<DateTime>("Date");
+            updates.Add(requi);
+        }
+        DbFactory.Provider.Save(updates);
+            
+        return true;
+    }
+    
+}

+ 75 - 0
prs.shared/Database Update Scripts/Update_6_39.cs

@@ -0,0 +1,75 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_6_39 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (6, 39);
+        
+    public override bool Update()
+    {
+                    
+        ConvertJobDocumentIssuedDates();
+            
+        ConvertProductUnitsOfMeasure();
+
+        ConvertQuoteUnitsOfMeasure();
+            
+        return true;
+    }
+    
+    void ConvertJobDocumentIssuedDates()
+    {
+        Logger.Send(LogType.Information, "", "Converting Job Document Issued Dates");
+        List<JobDocumentSetMileStone> updates = new List<JobDocumentSetMileStone>();
+        var columns = new Columns<JobDocumentSetMileStone>(x => x.ID).Add(x => x.Submitted).Add(x => x.Status);
+        columns.Add("Issued");
+        CoreTable milestones = DbFactory.Provider.Query<JobDocumentSetMileStone>(null, columns);
+        foreach (var row in milestones.Rows)
+        {
+            var milestone = row.ToObject<JobDocumentSetMileStone>();
+            if (milestone.Status == JobDocumentSetMileStoneStatus.Unknown)
+                milestone.Status = JobDocumentSetMileStoneStatus.Submitted;
+            milestone.Submitted = row.Get<DateTime>("Issued");
+            updates.Add(milestone);
+        }
+
+        DbFactory.Provider.Save(updates);
+    }
+    
+    void ConvertProductUnitsOfMeasure()
+    {
+        Logger.Send(LogType.Information, "", "Converting Product Units of Measure");
+        List<ProductDimensionUnit> updates = new List<ProductDimensionUnit>();
+        var columns = new Columns<ProductDimensionUnit>(x => x.ID).Add(x => x.Description);
+        CoreTable units = DbFactory.Provider.Query<ProductDimensionUnit>(new Filter<ProductDimensionUnit>(x=>x.Code).IsEqualTo(""), columns);
+        foreach (var row in units.Rows)
+        {
+            var unit = row.ToObject<ProductDimensionUnit>();
+            unit.Code = unit.Description;
+            updates.Add(unit);
+        }
+
+        DbFactory.Provider.Save(updates);
+    }
+    
+    void ConvertQuoteUnitsOfMeasure()
+    {
+        Logger.Send(LogType.Information, "", "Converting Quote Units of Measure");
+        List<QuoteTakeOffUnit> updates = new List<QuoteTakeOffUnit>();
+        var columns = new Columns<QuoteTakeOffUnit>(x => x.ID).Add(x => x.Description);
+        CoreTable units = DbFactory.Provider.Query<QuoteTakeOffUnit>(new Filter<QuoteTakeOffUnit>(x=>x.Code).IsEqualTo(""), columns);
+        foreach (var row in units.Rows)
+        {
+            var unit = row.ToObject<QuoteTakeOffUnit>();
+            unit.Code = unit.Description;
+            updates.Add(unit);
+        }
+
+        DbFactory.Provider.Save(updates);
+    }
+    
+}

+ 39 - 0
prs.shared/Database Update Scripts/Update_6_43.cs

@@ -0,0 +1,39 @@
+using System.Linq.Expressions;
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_6_43 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (6, 43);
+        
+    public override bool Update()
+    {
+        Logger.Send(LogType.Information, "", "Converting Supplier/Product Links");
+        List<SupplierProduct> updates = new List<SupplierProduct>();
+        var columns = new Columns<SupplierProduct>(x => x.ID).Add(x=>x.Product.ID);
+        columns.Add("ProductLink.ID");
+        CoreTable products = DbFactory.Provider.Query<SupplierProduct>(null, columns);
+        foreach (var row in products.Rows)
+        {
+            Guid id = row.Get<SupplierProduct,Guid>(x=>x.ID);
+            Guid oldid = row.Get<Guid>("ProductLink.ID");
+            Guid newid = row.Get<SupplierProduct,Guid>(x=>x.Product.ID);
+            if ((oldid != Guid.Empty) && (newid == Guid.Empty))
+            {
+                var update = new SupplierProduct() { ID = id };
+                update.CommitChanges();
+                update.Product.ID = oldid;
+                updates.Add(update);
+            }
+        }
+
+        DbFactory.Provider.Save(updates);
+
+        return true;
+    }
+    
+}

+ 103 - 0
prs.shared/Database Update Scripts/Update_7_00.cs

@@ -0,0 +1,103 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_7_00 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (7, 00);
+        
+    public override bool Update()
+    {
+        Convert<Assignment>(
+            new Filter<Assignment>(x=>x.Booked.Start).IsEqualTo(DateTime.MinValue)
+                .And(x=>x.Booked.Finish).IsEqualTo(DateTime.MinValue)
+                .And(x=>x.Actual.Finish).IsEqualTo(DateTime.MinValue)
+                .And(x=>x.Actual.Finish).IsEqualTo(DateTime.MinValue),
+            new Map<Assignment>("Start",x => x.Booked.Start),
+            new Map<Assignment>("Finish",x => x.Booked.Finish),
+            new Map<Assignment>("Start",x => x.Actual.Start),
+            new Map<Assignment>("Finish",x => x.Actual.Finish)
+        );
+        
+        // ConvertTimes<TimeSheet>(
+        //     x => x.Actual.Duration,
+        //     new TimeExpressions<TimeSheet>(x => x.Actual.Start, x => x.Actual.Finish),
+        //     new TimeExpressions<TimeSheet>(x => x.Approved.Start, x => x.Approved.Finish)
+        // );
+        
+        Convert_StandardLeaves_and_LeaveRequests();
+
+        return true;
+    }
+    
+    void Convert_StandardLeaves_and_LeaveRequests()
+    {
+        // Delete from TimeSheet where processed={} and leaverequestlink.id != empty
+        var unprocessedtimesheets = DbFactory.Provider.Query<TimeSheet>(
+            new Filter<TimeSheet>(x => x.Processed).IsEqualTo(DateTime.MinValue)
+                .And(x => x.LeaveRequestLink.ID).IsNotEqualTo(Guid.Empty),
+            new Columns<TimeSheet>(x=>x.ID)
+        ).Rows.Select(x=>x.ToObject<TimeSheet>()).ToArray();
+        
+        int iTimes = 0;
+        while (iTimes < unprocessedtimesheets.Length)
+        { 
+            var deletions = unprocessedtimesheets.Skip(iTimes).Take(100).ToArray();
+            DbFactory.Provider.Purge<TimeSheet>(deletions);
+            iTimes += deletions.Length;
+        }
+        //DbFactory.Provider.Delete<TimeSheet>(unprocessedtimesheets,"");
+
+        // Find all Leave Requests where public holiday != empty
+        var standardleaverequests = DbFactory.Provider.Query<LeaveRequest>(
+            new Filter<LeaveRequest>(x => x.PublicHoliday.ID).IsNotEqualTo(Guid.Empty),
+            new Columns<LeaveRequest>(x=>x.ID)
+                .Add(x=>x.PublicHoliday.ID)
+        ).Rows.Select(x => x.ToObject<LeaveRequest>()).ToArray();
+        foreach (var standardleaverequest in standardleaverequests)
+        {
+            // Find all timesheets for this leave request
+            var standardleavetimesheets = DbFactory.Provider.Query<TimeSheet>(
+                new Filter<TimeSheet>(x=>x.LeaveRequestLink.ID).IsEqualTo(standardleaverequest.ID),
+                new Columns<TimeSheet>(x=>x.ID)
+                    .Add(x=>x.LeaveRequestLink.ID)
+            ).Rows.Select(x=>x.ToObject<TimeSheet>()).ToArray();
+            
+            // Redirect timesheet from leaverequest to standardleave
+            foreach (var standardleavetimesheet in standardleavetimesheets)
+            {
+                standardleavetimesheet.StandardLeaveLink.ID = standardleaverequest.PublicHoliday.ID;
+                standardleavetimesheet.LeaveRequestLink.ID = Guid.Empty;
+            }
+            if (standardleavetimesheets.Any())
+                DbFactory.Provider.Save(standardleavetimesheets);
+        }
+        // delete these leave requests
+        int iRequests = 0;
+        while (iRequests < standardleaverequests.Length)
+        { 
+            var deletions = standardleaverequests.Skip(iRequests).Take(100).ToArray();
+            DbFactory.Provider.Purge<LeaveRequest>(deletions);
+            iRequests += deletions.Length;
+        }
+        
+        // Delete from Assignment where leaverequestlink id != empty
+        var leaveassignments = DbFactory.Provider.Query<Assignment>(
+            new Filter<Assignment>(x => x.LeaveRequestLink.ID).IsNotEqualTo(Guid.Empty),
+            new Columns<Assignment>(x=>x.ID)
+        ).Rows.Select(x=>x.ToObject<Assignment>()).ToArray();
+        
+        int iAssignments = 0;
+        while (iAssignments < leaveassignments.Length)
+        { 
+            var deletions = leaveassignments.Skip(iAssignments).Take(100).ToArray();
+            DbFactory.Provider.Purge<Assignment>(deletions);
+            iAssignments += deletions.Length;
+        }
+
+    }
+    
+}

+ 197 - 0
prs.shared/Database Update Scripts/Update_7_06.cs

@@ -0,0 +1,197 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_7_06 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (7,06);
+        
+    public override bool Update()
+    {
+        var deletions = DbFactory.Provider.Query<Deletion>(
+            new Filter<Deletion>(x => x.Data).IsEqualTo(""));
+        Logger.Send(LogType.Information, "", "Updating Deletions");
+        foreach (var deletion in deletions.ToObjects<Deletion>())
+        {
+            Purge(deletion);
+        }
+        Logger.Send(LogType.Information, "", "Finished updating Deletions");
+        return true;
+    }
+    
+    private static Dictionary<Type, List<Tuple<Type, string>>> _cascades = new();
+    private static Dictionary<Type, List<Tuple<Type, List<string>>>> _setNulls = new();
+
+    private static void LoadDeletions(Type type)
+    {
+        if (_cascades.ContainsKey(type)) return;
+
+        // Get the EntityLink that is associated with this class
+        var linkclass = CoreUtils.TypeList(
+            new[] { type.Assembly },
+            x => typeof(IEntityLink).GetTypeInfo().IsAssignableFrom(x) && x.GetInheritedGenericTypeArguments().FirstOrDefault() == type
+        ).FirstOrDefault();
+
+        // if The entitylink does not exist, we don't need to do anything
+        if (linkclass == null)
+            return;
+
+        var cascades = new List<Tuple<Type, string>>();
+        var setNulls = new List<Tuple<Type, List<string>>>();
+
+        var childtypes = DbFactory.Provider.Types.Where(x => x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute<AutoEntity>() == null);
+        foreach (var childtype in childtypes)
+        {
+            // Get all registered types for this entitylink
+            var fields = new List<string>();
+            var bDelete = false;
+
+            // Find any IEntityLink<> properties that refer back to this class
+            var childprops = CoreUtils.PropertyList(childtype, x => x.PropertyType == linkclass);
+
+            foreach (var childprop in childprops)
+            {
+                var fieldname = string.Format("{0}.ID", childprop.Name);
+                var attr = childprop.GetCustomAttributes(typeof(EntityRelationshipAttribute), true).FirstOrDefault();
+                if (attr != null && ((EntityRelationshipAttribute)attr).Action.Equals(DeleteAction.Cascade))
+                {
+                    cascades.Add(new(childtype, fieldname));
+                    bDelete = true;
+                    break;
+                }
+
+                fields.Add(fieldname);
+            }
+            if (!bDelete && fields.Any())
+            {
+                setNulls.Add(new(childtype, fields));
+            }
+        }
+
+        _cascades[type] = cascades;
+        _setNulls[type] = setNulls;
+    }
+    private static bool GetCascades(Type type, [NotNullWhen(true)] out List<Tuple<Type, string>>? cascades)
+    {
+        LoadDeletions(type);
+        return _cascades.TryGetValue(type, out cascades);
+    }
+    private static bool GetSetNulls(Type type, [NotNullWhen(true)] out List<Tuple<Type, List<string>>>? setNulls)
+    {
+        LoadDeletions(type);
+        return _setNulls.TryGetValue(type, out setNulls);
+    }
+
+
+    private static MethodInfo _deleteEntitiesMethod = typeof(Update_7_06).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
+        .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod);
+    private static void DeleteEntity<T>(Deletion deletion, Guid parentID, string parentField, DeletionData deletionData) where T : Entity, new()
+    {
+        var columns = DeletionData.DeletionColumns<T>();
+        var delEntities = DbFactory.Provider.QueryDeleted(deletion, new Filter<T>(parentField).IsEqualTo(parentID), columns);
+        var nDelntities = DbFactory.Provider.Query(new Filter<T>(parentField).IsEqualTo(parentID), columns);
+        foreach (var row in delEntities.Rows.Concat(nDelntities.Rows))
+        {
+            deletionData.DeleteEntity<T>(row);
+            CascadeDelete(typeof(T), deletion, row.Get<T, Guid>(x => x.ID), deletionData);
+        }
+    }
+
+    private static void DeleteEntity(Type T, Deletion deletion, Guid parentID, string parentField, DeletionData deletionData)
+    {
+        _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(null, new object?[] { deletion, parentID, parentField, deletionData });
+    }
+
+
+    private static MethodInfo _setNullEntityMethod = typeof(Update_7_06).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
+        .Single(x => x.Name == nameof(SetNullEntity) && x.IsGenericMethod);
+    private static void SetNullEntity<T>(List<string> properties, Guid parentID, DeletionData deletionData) where T : Entity, new()
+    {
+        foreach (var property in properties)
+        {
+            var entities = DbFactory.Provider.Query(new Filter<T>(property).IsEqualTo(parentID), new Columns<T>(x => x.ID));
+            foreach (var row in entities.Rows)
+            {
+                deletionData.SetNullEntity<T>(row.Get<T, Guid>(x => x.ID), property, parentID);
+            }
+        }
+    }
+
+    private static void SetNullEntity(Type T, List<string> properties, Guid parentID, DeletionData deletionData)
+    {
+        _setNullEntityMethod.MakeGenericMethod(T).Invoke(null, new object?[] { properties, parentID, deletionData });
+    }
+
+
+    private static void CascadeDelete(Type type, Deletion deletion, Guid parentID, DeletionData deletionData)
+    {
+        if (GetCascades(type, out var cascades))
+        {
+            foreach (var cascade in cascades)
+            {
+                DeleteEntity(cascade.Item1, deletion, parentID, cascade.Item2, deletionData);
+            }
+        }
+        if (GetSetNulls(type, out var setNulls))
+        {
+            foreach (var setNull in setNulls)
+            {
+                SetNullEntity(setNull.Item1, setNull.Item2, parentID, deletionData);
+            }
+        }
+    }
+
+    // Referenced via reflection.
+    private static void PurgeEntityType<T>(Deletion deletion) where T : Entity, new()
+    {
+        var entities = DbFactory.Provider.QueryDeleted(deletion, null, DeletionData.DeletionColumns<T>()).ToObjects<T>().ToList();
+
+        var deletionData = new DeletionData();
+        foreach (var entity in entities)
+        {
+            deletionData.DeleteEntity(entity);
+            CascadeDelete(typeof(T), deletion, entity.ID, deletionData);
+        }
+
+        if (deletionData.Cascades.Count > 0 || deletionData.SetNulls.Count > 0)
+        {
+            var tableName = typeof(T).Name;
+            var newDeletion = new Deletion()
+            {
+                DeletionDate = DateTime.Now,
+                HeadTable = tableName,
+                Description = $"Deleted {entities.Count} entries",
+                DeletedBy = deletion.DeletedBy,
+                Data = Serialization.Serialize(deletionData)
+            };
+            DbFactory.Provider.Save(newDeletion);
+        }
+        DbFactory.Provider.Purge(entities);
+    }
+
+    public static void Purge(Deletion deletion)
+    {
+        if (deletion.ID == Guid.Empty)
+        {
+            Logger.Send(LogType.Error, "", "Empty Deletion ID");
+            return;
+        }
+
+        var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
+        if (entityType is null)
+        {
+            Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist");
+            return;
+        }
+        var purgeMethod = typeof(Update_7_06).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!;
+        purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion });
+
+        DbFactory.Provider.Purge<Deletion>(deletion);
+    }
+
+
+}

+ 47 - 0
prs.shared/Database Update Scripts/Update_7_14.cs

@@ -0,0 +1,47 @@
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+/// <summary>
+/// Updating Wpf and Timebench fields to use Platform.DesktopVersion and Platform.MobileVersion
+/// </summary>
+public class Update_7_14 : DatabaseUpdateScript
+{
+
+    public override VersionNumber Version => new (7, 14);
+    
+    public override bool Update()
+    {
+        Logger.Send(LogType.Information, "", "Converting User.Wpf, User.Timebench -> User.Platform.DesktopVersion, User.Platform.MobileVersion");
+
+        Logger.Send(LogType.Information, "", "Loading Wpf, Timebench properties");
+        var props = DbFactory.Provider.Query<CustomProperty>(new Filter<CustomProperty>(x => x.Name).InList("Wpf", "TimeBench"))
+            .Rows.Select(x => x.ToObject<CustomProperty>()).ToArray();
+        DatabaseSchema.Load(props);
+
+        var columns = new Columns<User>(x => x.ID);
+        columns.Add("Wpf", "TimeBench");
+
+        var users = DbFactory.Provider.Query<User>(
+            new Filter<User>().All(),
+            columns).ToObjects<User>().ToList();
+        foreach(var user in users)
+        {
+            if(user.UserProperties.Dictionary.TryGetValue("Wpf", out var wpf))
+            {
+                user.Platform.DesktopVersion = wpf?.Value?.ToString() ?? "";
+            }
+            if (user.UserProperties.Dictionary.TryGetValue("TimeBench", out var timebench))
+            {
+                user.Platform.MobileVersion = timebench?.Value?.ToString() ?? "";
+            }
+        }
+        DbFactory.Provider.Save<User>(users);
+
+        Logger.Send(LogType.Information, "", "Finished updating user versions");
+
+        return true;
+    }
+    
+}

+ 64 - 0
prs.shared/Database Update Scripts/Update_7_19.cs

@@ -0,0 +1,64 @@
+using System.Text.RegularExpressions;
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+/// <summary>
+/// Trigger the calculation of Digital Form Numbers
+/// </summary>
+/// <returns>true (always)</returns>
+public class Update_7_19 : DatabaseUpdateScript
+{
+    public override VersionNumber Version => new VersionNumber(7, 19);
+
+    public override bool Update()
+    {
+        // Find all classes that derive from EntityForm
+        var formtypes = CoreUtils.TypeList(
+            new[]
+            {
+                typeof(Setout).Assembly
+            },
+            myType =>
+                myType is { IsClass: true, IsAbstract: false, IsGenericType: false } 
+                && myType.IsSubclassOf(typeof(Entity)) 
+                && myType.GetInterfaces().Contains(typeof(IEntityForm))
+        ).ToArray();
+            
+        foreach (var formtype in formtypes)
+        {
+            var columns = Columns.Create(formtype).Add("ID").Add("Number");
+            var forms = DbFactory.Provider.Query(formtype, null, columns);
+            var nullforms = forms.Rows
+                .Where(r => String.IsNullOrWhiteSpace(r.Get<String>("Number")))
+                .Select(r=>r.ToObject(formtype))
+                .OfType<Entity>()
+                .ToArray();
+
+            if (!nullforms.Any()) 
+                continue;
+
+            var maxvalue = forms.Rows.Select(r => Regex.Match(r.Get<String>("Number") ?? "", @"(?<=-)\d+")?.Value ?? "0")
+                .MaxBy(x => x) ?? "0";
+            int.TryParse(maxvalue, out int iNumber);
+
+            String prefix = null;
+            String format = null; 
+            foreach (var form in nullforms)
+            {
+                prefix ??= (form as IStringAutoIncrement)?.AutoIncrementPrefix() ?? "";
+                format ??= (form as IStringAutoIncrement)?.AutoIncrementFormat() ?? "{0:D8}";
+                iNumber++;
+                CoreUtils.SetPropertyValue(form, "Number", prefix+String.Format(format, iNumber));
+            }
+
+            DbFactory.Provider.Save(formtype, nullforms);
+
+        }
+        return true;
+    }
+    
+    
+}

+ 20 - 0
prs.shared/Database Update Scripts/Update_7_21.cs

@@ -0,0 +1,20 @@
+using InABox.Configuration;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+public class Update_7_21 : DatabaseUpdateScript
+{
+    public override VersionNumber Version => new(7, 21);
+    public override bool Update()
+    {
+        var filters = DbFactory.Provider.Query<GlobalSettings>(
+            new Filter<GlobalSettings>(x => x.Section).IsEqualTo("DynamicGridFilter")
+            ).Rows.Select(x=>x.ToObject<GlobalSettings>()).ToArray();
+        foreach (var filter in filters)
+            filter.Section = typeof(CoreFilterDefinitions).EntityName().Split('.').Last();
+        DbFactory.Provider.Save(filters);
+        return true;
+    }
+}

+ 0 - 629
prs.shared/DatabaseUpdateScripts.cs

@@ -1,629 +0,0 @@
-using Comal.Classes;
-using InABox.Core;
-using InABox.Database;
-using System;
-using System.Collections.Generic;
-using System.Configuration.Provider;
-using System.Linq;
-using System.Linq.Expressions;
-using System.Text;
-using System.Threading.Tasks;
-using Syncfusion.Windows.Tools.Controls;
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
-using System.Text.RegularExpressions;
-using FastReport.Utils;
-
-namespace PRS.Shared
-{
-    public static class DatabaseUpdateScripts
-    {
-        public static void RegisterScripts()
-        {
-            DataUpdater.RegisterUpdateScript("6.31", Update_6_31);
-            DataUpdater.RegisterUpdateScript("6.37", Update_6_37);
-            DataUpdater.RegisterUpdateScript("6.38", Update_6_38);
-            DataUpdater.RegisterUpdateScript("6.39", Update_6_39);
-            DataUpdater.RegisterUpdateScript("6.43", Update_6_43);
-            DataUpdater.RegisterUpdateScript("7.00", Update_7_00);
-            DataUpdater.RegisterUpdateScript("7.06", Update_7_06);
-            DataUpdater.RegisterUpdateScript("7.14", Update_7_14);
-            DataUpdater.RegisterUpdateScript("7.19", Update_7_19);
-        }
-
-        private static Dictionary<string, Tuple<string, string>> _6_31_module_map = new()
-        {
-            { "Assignments", new("Assignments", "Assignments") },
-            { "Daily Report", new("Daily Report", "Assignments") },
-            { "Delivered On Site", new("Delivered On Site", "Delivery Items") },
-            { "Deliveries", new("Deliveries", "Deliveries") },
-            { "Digital Forms", new("Digital Forms", "DigitalForm") },
-            { "Employee List", new("Employees", "Employee") },
-            { "Equipment List", new("Equipment", "Equipment") },
-            { "Factory Floor", new("Factory", "Manufacturing Packets") },
-            { "Incoming Consignments", new("Consignments", "Consignment") },
-            { "Manufacturing Status", new("Manufacturing Packets", "Manufacturing Packets") },
-            { "Product List", new("Products", "Products") },
-            { "Projects", new("Job Details", "Job Details") },
-            { "Purchase Orders", new("Purchase Orders", "PurchaseOrder") },
-            { "Quotes", new("Quotes", "Quotes") },
-            { "Rack List", new("Shipping", "Shipments") },
-            { "Site Requisitions", new("Requisitions", "Requisition") },
-            { "Staff TimeSheets", new("Timesheets", "TimeSheet") },
-            { "Stock Locations", new("Stock Locations", "StockLocation") },
-            { "Stock Movements", new("Stock Movements", "StockMovement") },
-            { "Task List", new("Tasks By Status", "Kanban") },
-        };
-        private static bool Update_6_31()
-        {
-            var modules = DbFactory.Provider.Query(new Filter<CustomModule>().All())
-                .Rows.Select(x => x.ToObject<CustomModule>()).ToList();
-
-            foreach(var module in modules)
-            {
-                if (!string.IsNullOrWhiteSpace(module.Section))
-                {
-                    if (_6_31_module_map.TryGetValue(module.Section, out var map))
-                    {
-                        module.Section = map.Item1;
-                        module.DataModel = map.Item2;
-                        module.AllRecords = true;
-                    }
-                    else
-                    {
-                        Logger.Send(LogType.Error, "", $"Custom Module '{module.Name}' has section name '{module.Section}' and will no longer be visible!");
-                    }
-                }
-            }
-            DbFactory.Provider.Save(modules);
-
-            return true;
-        }
-
-        private static bool Update_6_37()
-        {
-            Logger.Send(LogType.Information, "", "Recreating views");
-            DbFactory.Provider.ForceRecreateViews();
-            return true;
-        }
-        
-        private static bool Update_6_38()
-        {
-            
-            Logger.Send(LogType.Information, "", "Converting Job Requisition Dates to Due Dates");
-            List<JobRequisition> updates = new List<JobRequisition>();
-            var columns = new Columns<JobRequisition>(x => x.ID);
-            columns.Add("Date");
-            CoreTable requis = DbFactory.Provider.Query<JobRequisition>(null, columns);
-            foreach (var row in requis.Rows)
-            {
-                var requi = row.ToObject<JobRequisition>();
-                requi.Approved = row.Get<DateTime>("Date");
-                updates.Add(requi);
-            }
-            DbFactory.Provider.Save(updates);
-            
-            return true;
-        }
-        
-        private static bool Update_6_39()
-        {
-            void ConvertJobDocumentIssuedDates()
-            {
-                Logger.Send(LogType.Information, "", "Converting Job Document Issued Dates");
-                List<JobDocumentSetMileStone> updates = new List<JobDocumentSetMileStone>();
-                var columns = new Columns<JobDocumentSetMileStone>(x => x.ID).Add(x => x.Submitted).Add(x => x.Status);
-                columns.Add("Issued");
-                CoreTable milestones = DbFactory.Provider.Query<JobDocumentSetMileStone>(null, columns);
-                foreach (var row in milestones.Rows)
-                {
-                    var milestone = row.ToObject<JobDocumentSetMileStone>();
-                    if (milestone.Status == JobDocumentSetMileStoneStatus.Unknown)
-                        milestone.Status = JobDocumentSetMileStoneStatus.Submitted;
-                    milestone.Submitted = row.Get<DateTime>("Issued");
-                    updates.Add(milestone);
-                }
-
-                DbFactory.Provider.Save(updates);
-            }
-            
-            void ConvertProductUnitsOfMeasure()
-            {
-                Logger.Send(LogType.Information, "", "Converting Product Units of Measure");
-                List<ProductDimensionUnit> updates = new List<ProductDimensionUnit>();
-                var columns = new Columns<ProductDimensionUnit>(x => x.ID).Add(x => x.Description);
-                CoreTable units = DbFactory.Provider.Query<ProductDimensionUnit>(new Filter<ProductDimensionUnit>(x=>x.Code).IsEqualTo(""), columns);
-                foreach (var row in units.Rows)
-                {
-                    var unit = row.ToObject<ProductDimensionUnit>();
-                    unit.Code = unit.Description;
-                    updates.Add(unit);
-                }
-
-                DbFactory.Provider.Save(updates);
-            }
-            
-            void ConvertQuoteUnitsOfMeasure()
-            {
-                Logger.Send(LogType.Information, "", "Converting Quote Units of Measure");
-                List<QuoteTakeOffUnit> updates = new List<QuoteTakeOffUnit>();
-                var columns = new Columns<QuoteTakeOffUnit>(x => x.ID).Add(x => x.Description);
-                CoreTable units = DbFactory.Provider.Query<QuoteTakeOffUnit>(new Filter<QuoteTakeOffUnit>(x=>x.Code).IsEqualTo(""), columns);
-                foreach (var row in units.Rows)
-                {
-                    var unit = row.ToObject<QuoteTakeOffUnit>();
-                    unit.Code = unit.Description;
-                    updates.Add(unit);
-                }
-
-                DbFactory.Provider.Save(updates);
-            }
-            
-            ConvertJobDocumentIssuedDates();
-            
-            ConvertProductUnitsOfMeasure();
-
-            ConvertQuoteUnitsOfMeasure();
-            
-            return true;
-        }
-
-        private static bool Update_6_43()
-        {
-            void ConvertSupplierProductLinks()
-            {
-                Logger.Send(LogType.Information, "", "Converting Supplier/Product Links");
-                List<SupplierProduct> updates = new List<SupplierProduct>();
-                var columns = new Columns<SupplierProduct>(x => x.ID).Add(x=>x.Product.ID);
-                columns.Add("ProductLink.ID");
-                CoreTable products = DbFactory.Provider.Query<SupplierProduct>(null, columns);
-                foreach (var row in products.Rows)
-                {
-                    Guid id = row.Get<SupplierProduct,Guid>(x=>x.ID);
-                    Guid oldid = row.Get<Guid>("ProductLink.ID");
-                    Guid newid = row.Get<SupplierProduct,Guid>(x=>x.Product.ID);
-                    if ((oldid != Guid.Empty) && (newid == Guid.Empty))
-                    {
-                        var update = new SupplierProduct() { ID = id };
-                        update.CommitChanges();
-                        update.Product.ID = oldid;
-                        updates.Add(update);
-                    }
-                }
-
-                DbFactory.Provider.Save(updates);
-            }
-
-            ConvertSupplierProductLinks();
-
-            return true;
-        }
-
-        private struct Map<T>
-        {
-            public String Old;
-            public Expression<Func<T, object>> New;
-
-            public Map(String oldcolumn, Expression<Func<T, object>> newcolumn)
-            {
-                Old = oldcolumn;
-                New = newcolumn;
-            }
-        }
-        
-        private static bool Update_7_00()
-        {
-            
-            static void Convert<T>(
-                Filter<T> filter,
-                params Map<T>[] maps
-            ) where T : Entity, IPersistent, IRemotable, new()
-            {
-                Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()}...");
-                List<T> updates = new List<T>();
-                var columns = new Columns<T>(x => x.ID);
-                foreach (var map in maps)
-                {
-                    if (!columns.Items.Any(x=>String.Equals(x.Property,map.Old)))
-                        columns.Add(map.Old);
-                    
-                    if (!columns.Items.Any(x=>String.Equals(x.Property,CoreUtils.GetFullPropertyName<T, object>(map.New, "."))))
-                     columns.Add(map.New);
-                }
-
-                CoreTable table = DbFactory.Provider.Query<T>(filter,columns);
-                int iCount = 0;
-                foreach (var row in table.Rows)
-                {
-                    
-
-                    var update = row.ToObject<T>();
-                    foreach (var map in maps)
-                        CoreUtils.SetPropertyValue(update, CoreUtils.GetFullPropertyName<T, object>(map.New, "."), CoreUtils.GetPropertyValue(update, map.Old));
-                    if (update.IsChanged())
-                        updates.Add(update);
-            
-                    if (updates.Count == 100)
-                    {
-                        iCount += updates.Count;
-                        Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()} Times ({iCount}/{table.Rows.Count}");
-                        DbFactory.Provider.Save(updates);
-                        updates.Clear();
-                    }
-                }
-                
-                if (updates.Count > 0)
-                {
-                    iCount += updates.Count;
-                    Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()} Times ({iCount}/{table.Rows.Count})");
-                    DbFactory.Provider.Save(updates);
-                    updates.Clear();
-                }
-                
-            }
-
-            Convert<Assignment>(
-                new Filter<Assignment>(x=>x.Booked.Start).IsEqualTo(DateTime.MinValue)
-                    .And(x=>x.Booked.Finish).IsEqualTo(DateTime.MinValue)
-                    .And(x=>x.Actual.Finish).IsEqualTo(DateTime.MinValue)
-                    .And(x=>x.Actual.Finish).IsEqualTo(DateTime.MinValue),
-            new Map<Assignment>("Start",x => x.Booked.Start),
-                new Map<Assignment>("Finish",x => x.Booked.Finish),
-                new Map<Assignment>("Start",x => x.Actual.Start),
-                new Map<Assignment>("Finish",x => x.Actual.Finish)
-            );
-            
-            // ConvertTimes<TimeSheet>(
-            //     x => x.Actual.Duration,
-            //     new TimeExpressions<TimeSheet>(x => x.Actual.Start, x => x.Actual.Finish),
-            //     new TimeExpressions<TimeSheet>(x => x.Approved.Start, x => x.Approved.Finish)
-            // );
-            
-            void Convert_StandardLeaves_and_LeaveRequests()
-            {
-                // Delete from TimeSheet where processed={} and leaverequestlink.id != empty
-                var unprocessedtimesheets = DbFactory.Provider.Query<TimeSheet>(
-                    new Filter<TimeSheet>(x => x.Processed).IsEqualTo(DateTime.MinValue)
-                        .And(x => x.LeaveRequestLink.ID).IsNotEqualTo(Guid.Empty),
-                    new Columns<TimeSheet>(x=>x.ID)
-                ).Rows.Select(x=>x.ToObject<TimeSheet>()).ToArray();
-                
-                int iTimes = 0;
-                while (iTimes < unprocessedtimesheets.Length)
-                { 
-                    var deletions = unprocessedtimesheets.Skip(iTimes).Take(100).ToArray();
-                    DbFactory.Provider.Purge<TimeSheet>(deletions);
-                    iTimes += deletions.Length;
-                }
-                //DbFactory.Provider.Delete<TimeSheet>(unprocessedtimesheets,"");
-
-                // Find all Leave Requests where public holiday != empty
-                var standardleaverequests = DbFactory.Provider.Query<LeaveRequest>(
-                    new Filter<LeaveRequest>(x => x.PublicHoliday.ID).IsNotEqualTo(Guid.Empty),
-                    new Columns<LeaveRequest>(x=>x.ID)
-                        .Add(x=>x.PublicHoliday.ID)
-                ).Rows.Select(x => x.ToObject<LeaveRequest>()).ToArray();
-                foreach (var standardleaverequest in standardleaverequests)
-                {
-                    // Find all timesheets for this leave request
-                    var standardleavetimesheets = DbFactory.Provider.Query<TimeSheet>(
-                        new Filter<TimeSheet>(x=>x.LeaveRequestLink.ID).IsEqualTo(standardleaverequest.ID),
-                        new Columns<TimeSheet>(x=>x.ID)
-                            .Add(x=>x.LeaveRequestLink.ID)
-                    ).Rows.Select(x=>x.ToObject<TimeSheet>()).ToArray();
-                    
-                    // Redirect timesheet from leaverequest to standardleave
-                    foreach (var standardleavetimesheet in standardleavetimesheets)
-                    {
-                        standardleavetimesheet.StandardLeaveLink.ID = standardleaverequest.PublicHoliday.ID;
-                        standardleavetimesheet.LeaveRequestLink.ID = Guid.Empty;
-                    }
-                    if (standardleavetimesheets.Any())
-                        DbFactory.Provider.Save(standardleavetimesheets);
-                }
-                // delete these leave requests
-                int iRequests = 0;
-                while (iRequests < standardleaverequests.Length)
-                { 
-                    var deletions = standardleaverequests.Skip(iRequests).Take(100).ToArray();
-                    DbFactory.Provider.Purge<LeaveRequest>(deletions);
-                    iRequests += deletions.Length;
-                }
-                
-                // Delete from Assignment where leaverequestlink id != empty
-                var leaveassignments = DbFactory.Provider.Query<Assignment>(
-                    new Filter<Assignment>(x => x.LeaveRequestLink.ID).IsNotEqualTo(Guid.Empty),
-                    new Columns<Assignment>(x=>x.ID)
-                ).Rows.Select(x=>x.ToObject<Assignment>()).ToArray();
-                
-                int iAssignments = 0;
-                while (iAssignments < leaveassignments.Length)
-                { 
-                    var deletions = leaveassignments.Skip(iAssignments).Take(100).ToArray();
-                    DbFactory.Provider.Purge<Assignment>(deletions);
-                    iAssignments += deletions.Length;
-                }
-
-            }
-                       
-            Convert_StandardLeaves_and_LeaveRequests();
-
-            return true;
-        }
-        
-        private class Update_7_06_Class
-        {
-            private static Dictionary<Type, List<Tuple<Type, string>>> _cascades = new();
-            private static Dictionary<Type, List<Tuple<Type, List<string>>>> _setNulls = new();
-
-            private static void LoadDeletions(Type type)
-            {
-                if (_cascades.ContainsKey(type)) return;
-
-                // Get the EntityLink that is associated with this class
-                var linkclass = CoreUtils.TypeList(
-                    new[] { type.Assembly },
-                    x => typeof(IEntityLink).GetTypeInfo().IsAssignableFrom(x) && x.GetInheritedGenericTypeArguments().FirstOrDefault() == type
-                ).FirstOrDefault();
-
-                // if The entitylink does not exist, we don't need to do anything
-                if (linkclass == null)
-                    return;
-
-                var cascades = new List<Tuple<Type, string>>();
-                var setNulls = new List<Tuple<Type, List<string>>>();
-
-                var childtypes = DbFactory.Provider.Types.Where(x => x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute<AutoEntity>() == null);
-                foreach (var childtype in childtypes)
-                {
-                    // Get all registered types for this entitylink
-                    var fields = new List<string>();
-                    var bDelete = false;
-
-                    // Find any IEntityLink<> properties that refer back to this class
-                    var childprops = CoreUtils.PropertyList(childtype, x => x.PropertyType == linkclass);
-
-                    foreach (var childprop in childprops)
-                    {
-                        var fieldname = string.Format("{0}.ID", childprop.Name);
-                        var attr = childprop.GetCustomAttributes(typeof(EntityRelationshipAttribute), true).FirstOrDefault();
-                        if (attr != null && ((EntityRelationshipAttribute)attr).Action.Equals(DeleteAction.Cascade))
-                        {
-                            cascades.Add(new(childtype, fieldname));
-                            bDelete = true;
-                            break;
-                        }
-
-                        fields.Add(fieldname);
-                    }
-                    if (!bDelete && fields.Any())
-                    {
-                        setNulls.Add(new(childtype, fields));
-                    }
-                }
-
-                _cascades[type] = cascades;
-                _setNulls[type] = setNulls;
-            }
-            private static bool GetCascades(Type type, [NotNullWhen(true)] out List<Tuple<Type, string>>? cascades)
-            {
-                LoadDeletions(type);
-                return _cascades.TryGetValue(type, out cascades);
-            }
-            private static bool GetSetNulls(Type type, [NotNullWhen(true)] out List<Tuple<Type, List<string>>>? setNulls)
-            {
-                LoadDeletions(type);
-                return _setNulls.TryGetValue(type, out setNulls);
-            }
-
-
-            private static MethodInfo _deleteEntitiesMethod = typeof(Update_7_06_Class).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
-                .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod);
-            private static void DeleteEntity<T>(Deletion deletion, Guid parentID, string parentField, DeletionData deletionData) where T : Entity, new()
-            {
-                var columns = DeletionData.DeletionColumns<T>();
-                var delEntities = DbFactory.Provider.QueryDeleted(deletion, new Filter<T>(parentField).IsEqualTo(parentID), columns);
-                var nDelntities = DbFactory.Provider.Query(new Filter<T>(parentField).IsEqualTo(parentID), columns);
-                foreach (var row in delEntities.Rows.Concat(nDelntities.Rows))
-                {
-                    deletionData.DeleteEntity<T>(row);
-                    CascadeDelete(typeof(T), deletion, row.Get<T, Guid>(x => x.ID), deletionData);
-                }
-            }
-
-            private static void DeleteEntity(Type T, Deletion deletion, Guid parentID, string parentField, DeletionData deletionData)
-            {
-                _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(null, new object?[] { deletion, parentID, parentField, deletionData });
-            }
-
-
-            private static MethodInfo _setNullEntityMethod = typeof(Update_7_06_Class).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
-                .Single(x => x.Name == nameof(SetNullEntity) && x.IsGenericMethod);
-            private static void SetNullEntity<T>(List<string> properties, Guid parentID, DeletionData deletionData) where T : Entity, new()
-            {
-                foreach (var property in properties)
-                {
-                    var entities = DbFactory.Provider.Query(new Filter<T>(property).IsEqualTo(parentID), new Columns<T>(x => x.ID));
-                    foreach (var row in entities.Rows)
-                    {
-                        deletionData.SetNullEntity<T>(row.Get<T, Guid>(x => x.ID), property, parentID);
-                    }
-                }
-            }
-
-            private static void SetNullEntity(Type T, List<string> properties, Guid parentID, DeletionData deletionData)
-            {
-                _setNullEntityMethod.MakeGenericMethod(T).Invoke(null, new object?[] { properties, parentID, deletionData });
-            }
-
-
-            private static void CascadeDelete(Type type, Deletion deletion, Guid parentID, DeletionData deletionData)
-            {
-                if (GetCascades(type, out var cascades))
-                {
-                    foreach (var cascade in cascades)
-                    {
-                        DeleteEntity(cascade.Item1, deletion, parentID, cascade.Item2, deletionData);
-                    }
-                }
-                if (GetSetNulls(type, out var setNulls))
-                {
-                    foreach (var setNull in setNulls)
-                    {
-                        SetNullEntity(setNull.Item1, setNull.Item2, parentID, deletionData);
-                    }
-                }
-            }
-
-            // Referenced via reflection.
-            private static void PurgeEntityType<T>(Deletion deletion) where T : Entity, new()
-            {
-                var entities = DbFactory.Provider.QueryDeleted(deletion, null, DeletionData.DeletionColumns<T>()).ToObjects<T>().ToList();
-
-                var deletionData = new DeletionData();
-                foreach (var entity in entities)
-                {
-                    deletionData.DeleteEntity(entity);
-                    CascadeDelete(typeof(T), deletion, entity.ID, deletionData);
-                }
-
-                if (deletionData.Cascades.Count > 0 || deletionData.SetNulls.Count > 0)
-                {
-                    var tableName = typeof(T).Name;
-                    var newDeletion = new Deletion()
-                    {
-                        DeletionDate = DateTime.Now,
-                        HeadTable = tableName,
-                        Description = $"Deleted {entities.Count} entries",
-                        DeletedBy = deletion.DeletedBy,
-                        Data = Serialization.Serialize(deletionData)
-                    };
-                    DbFactory.Provider.Save(newDeletion);
-                }
-                DbFactory.Provider.Purge(entities);
-            }
-
-            public static void Purge(Deletion deletion)
-            {
-                if (deletion.ID == Guid.Empty)
-                {
-                    Logger.Send(LogType.Error, "", "Empty Deletion ID");
-                    return;
-                }
-
-                var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
-                if (entityType is null)
-                {
-                    Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist");
-                    return;
-                }
-                var purgeMethod = typeof(Update_7_06_Class).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!;
-                purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion });
-
-                DbFactory.Provider.Purge<Deletion>(deletion);
-            }
-        }
-
-        private static bool Update_7_06()
-        {
-            var deletions = DbFactory.Provider.Query<Deletion>(
-                new Filter<Deletion>(x => x.Data).IsEqualTo(""));
-            Logger.Send(LogType.Information, "", "Updating Deletions");
-            foreach (var deletion in deletions.ToObjects<Deletion>())
-            {
-                Update_7_06_Class.Purge(deletion);
-            }
-            Logger.Send(LogType.Information, "", "Finished updating Deletions");
-            return true;
-        }
-
-        /// <summary>
-        /// Updating Wpf and Timebench fields to use Platform.DesktopVersion and Platform.MobileVersion
-        /// </summary>
-        /// <returns></returns>
-        private static bool Update_7_14()
-        {
-            Logger.Send(LogType.Information, "", "Converting User.Wpf, User.Timebench -> User.Platform.DesktopVersion, User.Platform.MobileVersion");
-
-            Logger.Send(LogType.Information, "", "Loading Wpf, Timebench properties");
-            var props = DbFactory.Provider.Query<CustomProperty>(new Filter<CustomProperty>(x => x.Name).InList("Wpf", "TimeBench"))
-                .Rows.Select(x => x.ToObject<CustomProperty>()).ToArray();
-            DatabaseSchema.Load(props);
-
-            var columns = new Columns<User>(x => x.ID);
-            columns.Add("Wpf", "TimeBench");
-
-            var users = DbFactory.Provider.Query<User>(
-                new Filter<User>().All(),
-                columns).ToObjects<User>().ToList();
-            foreach(var user in users)
-            {
-                if(user.UserProperties.Dictionary.TryGetValue("Wpf", out var wpf))
-                {
-                    user.Platform.DesktopVersion = wpf?.Value?.ToString() ?? "";
-                }
-                if (user.UserProperties.Dictionary.TryGetValue("TimeBench", out var timebench))
-                {
-                    user.Platform.MobileVersion = timebench?.Value?.ToString() ?? "";
-                }
-            }
-            DbFactory.Provider.Save<User>(users);
-
-            Logger.Send(LogType.Information, "", "Finished updating user versions");
-
-            return true;
-        }
-
-        /// <summary>
-        /// Trigger the calculation of Digital Form Numbers
-        /// </summary>
-        /// <returns>true (always)</returns>
-        private static bool Update_7_19()
-        {
-            // Find all classes that derive from EntityForm
-            var formtypes = CoreUtils.TypeList(
-                new[]
-                {
-                    typeof(Setout).Assembly
-                },
-                myType =>
-                    myType is { IsClass: true, IsAbstract: false, IsGenericType: false } 
-                    && myType.IsSubclassOf(typeof(Entity)) 
-                    && myType.GetInterfaces().Contains(typeof(IEntityForm))
-            ).ToArray();
-            
-            foreach (var formtype in formtypes)
-            {
-                var columns = Columns.Create(formtype).Add("ID").Add("Number");
-                var forms = DbFactory.Provider.Query(formtype, null, columns);
-                var nullforms = forms.Rows
-                    .Where(r => String.IsNullOrWhiteSpace(r.Get<String>("Number")))
-                    .Select(r=>r.ToObject(formtype))
-                    .OfType<Entity>()
-                    .ToArray();
-
-                if (!nullforms.Any()) 
-                    continue;
-
-                var maxvalue = forms.Rows.Select(r => Regex.Match(r.Get<String>("Number") ?? "", @"(?<=-)\d+")?.Value ?? "0")
-                    .MaxBy(x => x) ?? "0";
-                int.TryParse(maxvalue, out int iNumber);
-
-                String prefix = null;
-                String format = null; 
-                foreach (var form in nullforms)
-                {
-                    prefix ??= (form as IStringAutoIncrement)?.AutoIncrementPrefix() ?? "";
-                    format ??= (form as IStringAutoIncrement)?.AutoIncrementFormat() ?? "{0:D8}";
-                    iNumber++;
-                    CoreUtils.SetPropertyValue(form, "Number", prefix+String.Format(format, iNumber));
-                }
-
-                DbFactory.Provider.Save(formtype, nullforms);
-
-            }
-            return true;
-        }
-        
-    }
-}