Browse Source

Switched the approvals script to be set on the ApprovalSet/
Moved approval management to context menu

Kenric Nugteren 5 months ago
parent
commit
d188a50c03

+ 6 - 1
prs.classes/Entities/Bill/Approvals/BillApproval.cs

@@ -5,17 +5,22 @@ using System.Text;
 
 namespace Comal.Classes
 {
-    public class BillApproval : Entity, ISequenceable, IRemotable, IPersistent, IOneToMany<Bill>, ILicense<AccountsPayableLicense>
+    public class BillApproval : Entity, ISequenceable, IRemotable, IPersistent, ILicense<AccountsPayableLicense>
     {
         [EditorSequence(1)]
+        [EntityRelationship(DeleteAction.Cascade)]
         public EmployeeLink Employee { get; set; }
 
         [NullEditor]
+        [EntityRelationship(DeleteAction.Cascade)]
         public BillLink Bill { get; set; }
 
         [EditorSequence(2)]
         public DateTime Approved { get; set; }
 
+        [NullEditor]
+        public bool IsCustom { get; set; }
+
         [NullEditor]
         public long Sequence { get; set; }
 

+ 25 - 1
prs.classes/Entities/Bill/Approvals/BillApprovalSet.cs

@@ -1,4 +1,5 @@
-using InABox.Core;
+using AutoProperties;
+using InABox.Core;
 using System;
 using System.Collections.Generic;
 using System.Text;
@@ -15,9 +16,31 @@ namespace Comal.Classes
         public string Description { get; set; }
 
         [EditorSequence(3)]
+        [ScriptEditor]
+        public string ApprovalSetUpdateScript { get; set; }
+
+        [InterceptIgnore]
+        [DoNotPersist]
+        [DoNotSerialize]
+        public static string UpdateBillApprovalsMethodName => "UpdateBillApprovals";
+
         [Editable(Editable.Hidden)]
         public bool IsDefault { get; set; }
 
+        public static string DefaultApprovalSetUpdateScript()
+        {
+            return @"
+using Comal.Classes;
+
+public class Module
+{
+    public void " + UpdateBillApprovalsMethodName + @"(Bill bill, List<BillApproval> approvals)
+    {
+        // Update the 'approvals' list whenever the 'bill' is updated.
+    }
+}";
+        }
+
         static BillApprovalSet()
         {
             DefaultColumns.Add<BillApprovalSet>(x => x.Code);
@@ -27,6 +50,7 @@ namespace Comal.Classes
 
     public class BillApprovalSetLink : EntityLink<BillApprovalSet>
     {
+        [RequiredColumn]
         [LookupEditor(typeof(BillApprovalSet))]
         public override Guid ID { get; set; }
 

+ 2 - 0
prs.classes/Entities/Bill/Approvals/BillApprovalSetEmployee.cs

@@ -7,8 +7,10 @@ namespace Comal.Classes
 {
     public class BillApprovalSetEmployee : Entity, IRemotable, IPersistent, IManyToMany<BillApprovalSet, Employee>, ISequenceable, ILicense<AccountsPayableLicense>
     {
+        [EntityRelationship(DeleteAction.Cascade)]
         public BillApprovalSetLink ApprovalSet { get; set; }
 
+        [EntityRelationship(DeleteAction.Cascade)]
         public EmployeeLink Employee { get; set; }
 
         [NullEditor]

+ 0 - 35
prs.classes/Entities/Bill/Approvals/BillApprovalSettings.cs

@@ -1,35 +0,0 @@
-using AutoProperties;
-using InABox.Configuration;
-using InABox.Core;
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Comal.Classes
-{
-    public class BillApprovalSettings : BaseObject, IGlobalConfigurationSettings
-    {
-        [EditorSequence(1)]
-        [ScriptEditor]
-        public string ApprovalSetUpdateScript { get; set; }
-
-        [InterceptIgnore]
-        [DoNotPersist]
-        [DoNotSerialize]
-        public static string UpdateBillApprovalsMethodName => "UpdateBillApprovals";
-
-        public static string DefaultApprovalSetUpdateScript()
-        {
-            return @"
-using Comal.Classes;
-
-public class Module
-{
-    public void " + UpdateBillApprovalsMethodName + @"(Bill bill, List<BillApproval> approvals)
-    {
-        // Update the 'approvals' list whenever the 'bill' is updated.
-    }
-}";
-        }
-    }
-}

+ 1 - 0
prs.classes/Entities/Bill/Bill.cs

@@ -49,6 +49,7 @@ namespace Comal.Classes
 
         [EditorSequence("Additional", 4)]
         [Editable(Editable.Disabled)]
+        [RequiredColumn]
         public BillApprovalSetLink ApprovalSet { get; set; }
 
         private class IsApprovedFormula : ComplexFormulaGenerator<Bill, bool>

+ 21 - 0
prs.desktop/Panels/Suppliers/Bills/BillApprovalSetGrid.cs

@@ -54,4 +54,25 @@ public class BillApprovalSetGrid : DynamicDataGrid<BillApprovalSet>
         SaveItems(toSave);
         return true;
     }
+
+    protected override void CustomiseEditor(BillApprovalSet[] items, DynamicGridColumn column, BaseEditor editor)
+    {
+        base.CustomiseEditor(items, column, editor);
+        if (column.ColumnName == nameof(BillApprovalSet.ApprovalSetUpdateScript) && editor is ScriptEditor scriptEditor)
+        {
+            scriptEditor.Type = ScriptEditorType.TemplateEditor;
+            scriptEditor.OnEditorClicked += () =>
+            {
+                var script = items.FirstOrDefault()?.ApprovalSetUpdateScript.NotWhiteSpaceOr()
+                             ?? BillApprovalSet.DefaultApprovalSetUpdateScript();
+
+                var editor = new ScriptEditorWindow(script, SyntaxLanguage.CSharp);
+                if (editor.ShowDialog() == true)
+                {
+                    foreach (var item in items)
+                        SetEditorValue(item, column.ColumnName, editor.Script);
+                }
+            };
+        }
+    }
 }

+ 0 - 34
prs.desktop/Panels/Suppliers/Bills/BillApprovalSettingsGrid.cs

@@ -1,34 +0,0 @@
-using Comal.Classes;
-using InABox.Core;
-using InABox.DynamicGrid;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace PRSDesktop.Panels.Suppliers.Bills;
-
-public class BillApprovalSettingsGrid : DynamicItemsListGrid<BillApprovalSettings>
-{
-    protected override void CustomiseEditor(BillApprovalSettings[] items, DynamicGridColumn column, BaseEditor editor)
-    {
-        base.CustomiseEditor(items, column, editor);
-        if (column.ColumnName == nameof(BillApprovalSettings.ApprovalSetUpdateScript) && editor is ScriptEditor scriptEditor)
-        {
-            scriptEditor.Type = ScriptEditorType.TemplateEditor;
-            scriptEditor.OnEditorClicked += () =>
-            {
-                var script = items.FirstOrDefault()?.ApprovalSetUpdateScript.NotWhiteSpaceOr()
-                             ?? BillApprovalSettings.DefaultApprovalSetUpdateScript();
-
-                var editor = new ScriptEditorWindow(script, SyntaxLanguage.CSharp);
-                if (editor.ShowDialog() == true)
-                {
-                    foreach (var item in items)
-                        SetEditorValue(item, column.ColumnName, editor.Script);
-                }
-            };
-        }
-    }
-}

+ 128 - 6
prs.desktop/Panels/Suppliers/Bills/SupplierBillApprovalGrid.cs

@@ -1,30 +1,152 @@
 using Comal.Classes;
 using InABox.Core;
 using InABox.DynamicGrid;
+using InABox.WPF;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Threading;
 using System.Threading.Tasks;
+using System.Windows.Controls;
+using System.Windows.Media;
 
 namespace PRSDesktop;
 
-public class SupplierBillApprovalGrid : DynamicOneToManyGrid<Bill, BillApproval>
+public class SupplierBillApprovalGrid : DynamicDataGrid<BillApproval>, ISpecificGrid
 {
-    public override bool Visible => Security.IsAllowed<ManageBillApprovals>();
+    private Bill Bill;
 
-    public SupplierBillApprovalGrid()
+    public SupplierBillApprovalGrid(Bill bill)
     {
+        Bill = bill;
+    }
+
+    private Button ApproveButton = null!;
+
+    protected override void Init()
+    {
+        base.Init();
+
         HiddenColumns.Add(x => x.Employee.ID);
+        HiddenColumns.Add(x => x.IsCustom);
+
+        ApproveButton = AddButton("Mark as Approved", null, ApproveButton_Click);
+        ApproveButton.IsEnabled = false;
+    }
+
+    private bool ApproveButton_Click(Button button, CoreRow[] rows)
+    {
+        var items = LoadItems(rows);
+        if(items.Any(x => x.Approved == DateTime.MinValue))
+        {
+            foreach(var item in items)
+            {
+                item.Approved = DateTime.Now;
+            }
+        }
+        else
+        {
+            foreach(var item in items)
+            {
+                item.Approved = DateTime.MinValue;
+            }
+        }
+        SaveItems(items);
+        DoChanged();
+        return true;
+    }
+
+    protected override void SelectItems(CoreRow[]? rows)
+    {
+        base.SelectItems(rows);
+
+        if(rows is null || rows.Length == 0)
+        {
+            ApproveButton.IsEnabled = false;
+        }
+        else
+        {
+            ApproveButton.IsEnabled = true;
+            if(rows.Any(x => x.Get<BillApproval, DateTime>(x => x.Approved) == DateTime.MinValue))
+            {
+                ApproveButton.Content = "Mark as Approved";
+            }
+            else
+            {
+                ApproveButton.Content = "Mark as Unapproved";
+            }
+        }
+    }
+
+    #region UI Component
+
+    private class UIComponent : DynamicGridGridUIComponent<BillApproval>
+    {
+        protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
+        {
+            if(row.Get<BillApproval, bool>(x => x.IsCustom))
+            {
+                return null;
+            }
+            else
+            {
+                return Colors.Gainsboro.ToBrush(0.5);
+            }
+        }
+    }
+    protected override IDynamicGridUIComponent<BillApproval> CreateUIComponent()
+    {
+        return new UIComponent { Parent = this };
     }
 
+    #endregion
+
     protected override void DoReconfigure(DynamicGridOptions options)
     {
         base.DoReconfigure(options);
 
-        options.AddRows = !ReadOnly && Security.IsAllowed<ManageBillApprovals>();
-        options.EditRows = !ReadOnly && Security.IsAllowed<ManageBillApprovals>();
-        options.DeleteRows = !ReadOnly && Security.IsAllowed<ManageBillApprovals>();
+        options.AddRows = Security.IsAllowed<ManageBillApprovals>();
+        options.EditRows = Security.IsAllowed<ManageBillApprovals>();
+        options.DeleteRows = Security.IsAllowed<ManageBillApprovals>();
+    }
+
+    public override BillApproval CreateItem()
+    {
+        var item = base.CreateItem();
+        item.Bill.CopyFrom(Bill);
+        item.IsCustom = true;
+        return item;
+    }
+
+    protected override void Reload(Filters<BillApproval> criteria, Columns<BillApproval> columns, ref SortOrder<BillApproval>? sort, CancellationToken token, Action<CoreTable?, Exception?> action)
+    {
+        criteria.Add(new Filter<BillApproval>(x => x.Bill.ID).IsEqualTo(Bill.ID));
+        base.Reload(criteria, columns, ref sort, token, action);
+    }
+
+    protected override void DoEdit()
+    {
+        if(SelectedRows.Any(x => !x.Get<BillApproval, bool>(x => x.IsCustom)))
+        {
+            return;
+        }
+        else
+        {
+            base.DoEdit();
+        }
+    }
+
+    protected override void DoDelete()
+    {
+        if(SelectedRows.Any(x => !x.Get<BillApproval, bool>(x => x.IsCustom)))
+        {
+            return;
+        }
+        else
+        {
+            base.DoDelete();
+        }
     }
 
     protected override void DoAdd(bool openEditorOnDirectEdit = false)

+ 12 - 39
prs.desktop/Panels/Suppliers/Bills/SupplierBillPanel.xaml.cs

@@ -81,14 +81,6 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
             var list = new MasterList(typeof(BillApprovalSet));
             list.ShowDialog();
         });
-        host.CreateSetupActionIf<ManageBillApprovals>("Bill Approval Settings", PRSDesktop.Resources.edit, (action) =>
-        {
-            var settings = new GlobalConfiguration<BillApprovalSettings>().Load();
-            if (DynamicGridUtils.EditEntity(settings))
-            {
-                new GlobalConfiguration<BillApprovalSettings>().Save(settings);
-            }
-        });
     }
 
     public string SectionName => "Supplier Bills";
@@ -130,7 +122,6 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
     #region Approval
 
     private BillApproval? _approval = null;
-    private DynamicOneToManyGrid<Bill, BillApproval>? _approvalPage = null;
     private SupplierBillLineGrid? _billLinePage = null;
 
     private void EditLayout_Approve()
@@ -148,18 +139,10 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
             EditLayout.IsApproved = false;
         }
         Bill.DoChanged();
-
-        if(_approvalPage is not null)
-        {
-            _approvalPage.SaveItem(_approval);
-            _approvalPage.Refresh(false, true);
-            _approvalPage.DoChanged();
-        }
     }
 
     private void SaveApproval()
     {
-        if (_approvalPage is not null || _approval is null) return;
         Client.Save(_approval, "Approval updated by user.");
     }
 
@@ -172,20 +155,11 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
     {
         if(_bills is not null && _bills.Length == 1)
         {
-            if(Bill.Pages.TryGetPage<DynamicOneToManyGrid<Bill, BillApproval>>(out _approvalPage))
-            {
-                _approval = _approvalPage.Items.FirstOrDefault(x => x.Employee.ID == App.EmployeeID);
-                _approvalPage.OnChanged -= _approvalPage_OnChanged;
-                _approvalPage.OnChanged += _approvalPage_OnChanged;
-            }
-            else
-            {
-                _approval = Client.Query<BillApproval>(
-                    new Filter<BillApproval>(x => x.Employee.ID).IsEqualTo(App.EmployeeID)
-                        .And(x => x.Bill.ID).IsEqualTo(_bills[0].ID),
-                    Columns.Required<BillApproval>().Add(x => x.Approved))
-                    .ToObjects<BillApproval>().FirstOrDefault();
-            }
+            _approval = Client.Query<BillApproval>(
+                new Filter<BillApproval>(x => x.Employee.ID).IsEqualTo(App.EmployeeID)
+                    .And(x => x.Bill.ID).IsEqualTo(_bills[0].ID),
+                Columns.Required<BillApproval>().Add(x => x.Approved))
+                .ToObjects<BillApproval>().FirstOrDefault();
 
             EditLayout.CanApprove = _approval is not null;
             EditLayout.IsApproved = _approval is not null ? _approval.Approved != DateTime.MinValue : false;
@@ -209,12 +183,6 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
         }
     }
 
-    private void _approvalPage_OnChanged(object? sender, EventArgs e)
-    {
-        EditLayout.CanApprove = _approval is not null;
-        EditLayout.IsApproved = _approval is not null ? _approval.Approved != DateTime.MinValue : false;
-    }
-
     private void BillLinePage_OnChanged(object? sender, EventArgs e)
     {
         if (_billLinePage is null) return;
@@ -280,12 +248,12 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
         }
     }
 
-    private void ReloadBills()
+    private void ReloadBills(bool force = false)
     {
         var newRows = Bills.SelectedRows;
         if (newRows.Length != 0)
         {
-            if(_editRows is null || !newRows.CompareTo(_editRows))
+            if(force || _editRows is null || !newRows.CompareTo(_editRows))
             {
                 _editRows = Bills.SelectedRows;
                 _bills = Bills.LoadBills(_editRows);
@@ -550,6 +518,11 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
             editor.Page = "Dates";
         }
     }
+
+    private void Bills_OnChanged(object sender, EventArgs e)
+    {
+        ReloadBills(force: true);
+    }
 }
 
 public class BillDocumentViewList : DocumentViewList<BillDocument>

+ 26 - 66
prs.desktop/Panels/Suppliers/Bills/SupplierBills.cs

@@ -85,75 +85,35 @@ public class SupplierBills : DynamicDataGrid<Bill>
                 item.IsChecked = approval.ID == selectedApproval;
             }
         }, TaskScheduler.FromCurrentSynchronizationContext());
+
+        if (Security.IsAllowed<ManageBillApprovals>())
+        {
+            menu.AddItem("Manage Approvals", null, row, ManageApprovals_Click);
+        }
     }
 
-    private void ApprovalSet_Click((CoreRow row, BillApprovalSet approvalSet) item)
+    private void ManageApprovals_Click(CoreRow row)
     {
-        var bill = item.row.ToObject<Bill>();
-        if (bill.ApprovalSet.ID == item.approvalSet.ID) return;
+        var bill = row.ToObject<Bill>();
 
-        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
+        var grid = new SupplierBillApprovalGrid(bill);
+        var dlg = new DynamicContentDialog(grid, buttonsvisible: false)
         {
-            oldEmployees = [];
-        }
+            Title = "Manage Approvals"
+        };
+        grid.Refresh(true, true);
+        dlg.ShowDialog();
 
-        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))
-            {
-                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 = LoadItem(row);
+        UpdateRow(row, bill);
+
+        DoChanged();
+    }
 
+    private void ApprovalSet_Click((CoreRow row, BillApprovalSet approvalSet) item)
+    {
+        var bill = item.row.ToObject<Bill>();
+        if (bill.ApprovalSet.ID == item.approvalSet.ID) return;
         bill.ApprovalSet.CopyFrom(item.approvalSet);
         SaveItem(bill);
         Refresh(false, true);
@@ -174,10 +134,10 @@ public class SupplierBills : DynamicDataGrid<Bill>
         {
             docGrid.Order = 1;
         }
-        if(pages.TryGetPage<DynamicOneToManyGrid<Bill, BillApproval>>(out var approvalGrid))
-        {
-            approvalGrid.Order = 2;
-        }
+        // if(pages.TryGetPage<DynamicOneToManyGrid<Bill, BillApproval>>(out var approvalGrid))
+        // {
+        //     approvalGrid.Order = 2;
+        // }
         
         return pages;
     }

+ 68 - 0
prs.stores/BillApprovalSetStore.cs

@@ -0,0 +1,68 @@
+using com.sun.net.ssl.@internal.ssl;
+using Comal.Classes;
+using Comal.Stores;
+using InABox.Core;
+using InABox.Database;
+using InABox.Scripting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSStores;
+
+public class BillApprovalSetStore : BaseStore<BillApprovalSet>
+{
+    private static Dictionary<Guid, ScriptDocument?> _scripts = new();
+
+    protected override void BeforeSave(BillApprovalSet entity)
+    {
+        base.BeforeSave(entity);
+
+        if(entity.HasOriginalValue(x => x.ApprovalSetUpdateScript))
+        {
+            UpdateApprovalSetScript(entity.ID, entity.ApprovalSetUpdateScript);
+        }
+    }
+
+    private static ScriptDocument? UpdateApprovalSetScript(Guid approvalSetID, string script)
+    {
+        ScriptDocument? document = null;
+        if (!script.IsNullOrWhiteSpace())
+        {
+            document = new ScriptDocument(script);
+            if (!document.Compile())
+            {
+                InABox.Core.Logger.Send(LogType.Error, "", $"Bill Approval Script failed to compile: {document.Result}");
+                document = null;
+            }
+        }
+        lock(_scripts)
+        {
+            _scripts[approvalSetID] = document;
+        }
+        return document;
+    }
+
+    public static ScriptDocument? GetUpdateApprovalsScript(Guid approvalSetID)
+    {
+        lock (_scripts)
+        {
+            if (_scripts.TryGetValue(approvalSetID, out var script)) return script;
+        }
+
+        var set = DbFactory.NewProvider(Logger.Main).Query<BillApprovalSet>(
+            new Filter<BillApprovalSet>(x => x.ID).IsEqualTo(approvalSetID),
+            Columns.None<BillApprovalSet>().Add(x => x.ApprovalSetUpdateScript))
+            .ToObjects<BillApprovalSet>().FirstOrDefault();
+        if(set is not null)
+        {
+            return UpdateApprovalSetScript(approvalSetID, set.ApprovalSetUpdateScript);
+        }
+        else
+        {
+            return null;
+        }
+    }
+}

+ 59 - 84
prs.stores/BillStore.cs

@@ -1,44 +1,16 @@
-using Comal.Classes;
+using com.sun.org.glassfish.external.statistics;
+using Comal.Classes;
 using InABox.Configuration;
 using InABox.Core;
 using InABox.Database.Stores;
 using InABox.Scripting;
+using PRSStores;
 using System;
 
 namespace Comal.Stores;
 
 internal class BillStore : BaseStore<Bill>
 {
-    private static ScriptDocument? _billApprovalScriptDocument;
-    private static string? _billApprovalScript;
-    public static string? BillApprovalScript
-    {
-        set
-        {
-            _billApprovalScript = value;
-            if(!value.IsNullOrWhiteSpace())
-            {
-                _billApprovalScriptDocument = new ScriptDocument(value);
-                if (!_billApprovalScriptDocument.Compile())
-                {
-                    InABox.Core.Logger.Send(LogType.Error, "", $"Bill Approval Script failed to compile: {_billApprovalScriptDocument.Result}");
-                    _billApprovalScriptDocument = null;
-                }
-            }
-            else
-            {
-                _billApprovalScriptDocument = null;
-            }
-        }
-    }
-
-    public override void Init()
-    {
-        base.Init();
-
-        GlobalSettingsStore.RegisterSubStore<BillApprovalSettings, BillApprovalSettingsStore>();
-    }
-
     protected override void BeforeSave(Bill entity)
     {
         base.BeforeSave(entity);
@@ -55,7 +27,6 @@ internal class BillStore : BaseStore<Bill>
                 throw new DuplicateCodeException(typeof(Bill), new Dictionary<string, object> {{ nameof(Bill.Number), entity.Number }});
             }
         }
-        UpdateBillApprovals(entity);
 
         if (entity.ID == Guid.Empty)
         {
@@ -74,40 +45,77 @@ internal class BillStore : BaseStore<Bill>
 
     private void UpdateBillApprovals(Bill bill)
     {
-        if (bill.ID == Guid.Empty || _billApprovalScriptDocument is null) return;
+        if (bill.ID == Guid.Empty || bill.ApprovalSet.ID == Guid.Empty) return;
 
+        var setEmployees = Provider.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>();
         var approvals = Provider.Query(
             new Filter<BillApproval>(x => x.Bill.ID).IsEqualTo(bill.ID),
-            Columns.Required<BillApproval>())
+            Columns.Required<BillApproval>().Add(x => x.Employee.ID).Add(x => x.IsCustom).Add(x => x.Approved))
             .ToList<BillApproval>();
+
         try
         {
-            var oldList = approvals.ToList();
-            if(_billApprovalScriptDocument.Execute(methodname: BillApprovalSettings.UpdateBillApprovalsMethodName, parameters: [bill, approvals]))
+            var customApprovals = new List<BillApproval>();
+            foreach(var approval in approvals)
             {
-                var newApprovals = new List<BillApproval>();
-                var removedApprovals = new List<BillApproval>();
-                foreach(var approval in approvals)
+                if (approval.IsCustom)
                 {
-                    if (!oldList.Contains(approval))
-                    {
-                        newApprovals.Add(approval);
-                    }
+                    customApprovals.Add(approval);
                 }
-                foreach(var approval in oldList)
+                else
                 {
-                    if (!approvals.Contains(approval))
+                    if(approval.Approved != DateTime.MinValue)
                     {
-                        removedApprovals.Add(approval);
+                        // These really are custom approvals for our purposes, since they've been modified.
+                        customApprovals.Add(approval);
                     }
                 }
-                foreach(var approval in newApprovals)
+            }
+
+            // Build up the automatic list.
+            var autoApprovals = new List<BillApproval>();
+            foreach(var employee in setEmployees)
+            {
+                var approval = new BillApproval();
+                approval.Employee.CopyFrom(employee.Employee);
+                approval.IsCustom = false;
+                autoApprovals.Add(approval);
+            }
+            if(BillApprovalSetStore.GetUpdateApprovalsScript(bill.ApprovalSet.ID) is ScriptDocument script)
+            {
+                script.Execute(methodname: BillApprovalSet.UpdateBillApprovalsMethodName, parameters: [bill, autoApprovals]);
+            }
+
+            // Now, we have to merge the custom and auto lists, only saving auto approvals if there are no custom ones for that employee.
+            var toAdd = new List<BillApproval>();
+            var toRemove = new List<BillApproval>();
+
+            var newList = customApprovals.ToList();
+            foreach(var approval in autoApprovals)
+            {
+                newList.Add(approval);
+                if (!approvals.Any(x => x.Employee.ID == approval.Employee.ID))
+                {
+                    toAdd.Add(approval);
+                }
+            }
+            foreach(var approval in approvals)
+            {
+                if (!newList.Any(x => x.Employee.ID == approval.Employee.ID))
                 {
-                    approval.Bill.ID = bill.ID;
+                    toRemove.Add(approval);
                 }
-                Provider.Save(newApprovals);
-                Provider.Delete(removedApprovals, UserID);
             }
+            foreach(var approval in toAdd)
+            {
+                approval.Bill.ID = bill.ID;
+            }
+            Provider.Save(toAdd);
+            Provider.Delete(toRemove, UserID);
         }
         catch(Exception e)
         {
@@ -119,19 +127,7 @@ internal class BillStore : BaseStore<Bill>
     {
         base.AfterSave(entity);
 
-        if(entity.HasOriginalValue(x => x.ID) && entity.ApprovalSet.ID != Guid.Empty)
-        {
-            var employees = Provider.Query<BillApprovalSetEmployee>(
-                new Filter<BillApprovalSetEmployee>(x => x.ApprovalSet.ID).IsEqualTo(entity.ApprovalSet.ID),
-                Columns.None<BillApprovalSetEmployee>().Add(x => x.Employee.ID));
-            Provider.Save(employees.ToObjects<BillApprovalSetEmployee>().Select(x =>
-            {
-                var approval = new BillApproval();
-                approval.Bill.CopyFrom(entity);
-                approval.Employee.CopyFrom(x.Employee);
-                return approval;
-            }));
-        }
+        UpdateBillApprovals(entity);
     }
 
     protected override void BeforeDelete(Bill entity)
@@ -141,25 +137,4 @@ internal class BillStore : BaseStore<Bill>
         entity.SupplierLink.ID = Guid.Empty;
         //UpdateAggregate<Supplier>(entity, entity.SupplierLink, Sum<Supplier>(b => b.Balance, s => s.Balance));
     }
-}
-
-internal class BillApprovalSettingsStore : ISettingsStoreEventHandler<BillApprovalSettings>
-{
-    public void BeforeSave(BillApprovalSettings entity)
-    {
-    }
-    public void AfterSave(BillApprovalSettings entity)
-    {
-        ConfigurationCache.Add(ConfigurationCacheType.Global, "", entity);
-        BillStore.BillApprovalScript = entity.ApprovalSetUpdateScript;
-    }
-
-    public void BeforeDelete(BillApprovalSettings entity)
-    {
-    }
-    public void AfterDelete(BillApprovalSettings entity)
-    {
-        ConfigurationCache.Clear<BillApprovalSettings>(ConfigurationCacheType.Global, "");
-        BillStore.BillApprovalScript = null;
-    }
 }