Переглянути джерело

Moved KanbanSubscriber functionality into KanbanStore, and moved KanbanSubscriberGrid into Task context menu

Kenric Nugteren 6 місяців тому
батько
коміт
41239d2d1f

+ 4 - 3
prs.classes/Entities/Kanban/ClientKanbanSubscriberSet.cs

@@ -15,11 +15,12 @@ namespace Comal.Classes
         {
             _subscribers.Clear();
             var result = DoQuery(
-                new Filter<KanbanSubscriber>(x => x.Kanban.ID).InList(kanbanids.ToArray()),
-                Columns.None<KanbanSubscriber>().Add(x => x.ID).Add(x => x.Kanban.ID).Add(x => x.Employee.ID).Add(x => x.Assignee).Add(x => x.Manager)
+                new Filter<KanbanSubscriber>(x => x.Kanban.ID).InList(kanbanids.AsArray()),
+                Columns.None<KanbanSubscriber>()
+                    .Add(x => x.ID).Add(x => x.Kanban.ID).Add(x => x.Employee.ID).Add(x => x.Assignee).Add(x => x.Manager)
                     .Add(x => x.Observer));
             if (result.Rows.Any())
-                _subscribers.AddRange(result.Rows.Select(x => x.ToObject<KanbanSubscriber>()));
+                _subscribers.AddRange(result.ToObjects<KanbanSubscriber>());
         }
         
         public void Save(bool wait = true)

+ 9 - 1
prs.classes/Entities/Kanban/KanbanSubscriber.cs

@@ -3,7 +3,7 @@
 namespace Comal.Classes
 {
     [Caption("Subscribers")]
-    public class KanbanSubscriber : Entity, IRemotable, IPersistent, IOneToMany<Kanban>, ILicense<TaskManagementLicense>
+    public class KanbanSubscriber : Entity, IRemotable, IPersistent, ILicense<TaskManagementLicense>
     {
         [NullEditor]
         [EntityRelationship(DeleteAction.Cascade)]
@@ -20,5 +20,13 @@ namespace Comal.Classes
 
         [NullEditor]
         public bool Observer { get; set; }
+
+        static KanbanSubscriber()
+        {
+            DefaultColumns.Add<KanbanSubscriber>(x => x.Assignee);
+            DefaultColumns.Add<KanbanSubscriber>(x => x.Manager);
+            DefaultColumns.Add<KanbanSubscriber>(x => x.Employee.Code);
+            DefaultColumns.Add<KanbanSubscriber>(x => x.Employee.Name);
+        }
     }
 }

+ 88 - 118
prs.desktop/Panels/Tasks/KanbanSubscriberGrid.cs

@@ -1,158 +1,128 @@
 using System;
 using System.Linq;
+using System.Threading;
 using System.Windows;
 using System.Windows.Controls;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
 using InABox.DynamicGrid;
+using InABox.Wpf;
 using InABox.WPF;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class KanbanSubscriberGrid(Kanban kanban) : DynamicDataGrid<KanbanSubscriber>
 {
-    public class KanbanSubscriberGrid : DynamicOneToManyGrid<Kanban, KanbanSubscriber>
+    public Kanban Kanban { get; set; } = kanban;
+
+    protected override void Init()
     {
-        protected override void Init()
-        {
-            base.Init();
+        base.Init();
 
-            HiddenColumns.Add(x => x.Manager);
-            HiddenColumns.Add(x => x.Assignee);
-            HiddenColumns.Add(x => x.Observer);
-            HiddenColumns.Add(x => x.Employee.ID);
+        HiddenColumns.Add(x => x.Manager);
+        HiddenColumns.Add(x => x.Assignee);
+        HiddenColumns.Add(x => x.Observer);
+        HiddenColumns.Add(x => x.Employee.ID);
 
-            AddButton("Select Team", PRSDesktop.Resources.team.AsBitmapImage(), SelectTeamClick);
-        }
+        AddButton("Select Team", PRSDesktop.Resources.team.AsBitmapImage(), SelectTeamClick);
+    }
 
-        protected override void DoReconfigure(DynamicGridOptions options)
-        {
-            base.DoReconfigure(options);
-            options.RecordCount = true;
-            options.AddRows = true;
-            options.DeleteRows = true;
-            options.SelectColumns = true;
-            options.MultiSelect = true;
-        }
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
+        options.RecordCount = true;
+        options.AddRows = true;
+        options.DeleteRows = true;
+        options.SelectColumns = true;
+        options.MultiSelect = true;
+    }
+
+    public override KanbanSubscriber CreateItem()
+    {
+        var result = base.CreateItem();
+        result.Observer = true;
+        return result;
+    }
 
-        public override KanbanSubscriber CreateItem()
+    protected override void DoEdit()
+    {
+        if(SelectedRows.Any(r => r.Get<KanbanSubscriber, bool>(c => c.Manager) || r.Get<KanbanSubscriber, bool>(c => c.Assignee)))
         {
-            var result = base.CreateItem();
-            result.Observer = true;
-            return result;
+            MessageWindow.ShowMessage("You cannot edit an Assignee or Manager!", "Invalid operation");
+            return;
         }
+        base.DoEdit();
+    }
 
-        protected override void AfterLoad(IDynamicEditorForm editor, KanbanSubscriber[] items)
+    protected override bool CanDeleteItems(params CoreRow[] rows)
+    {
+        var result = base.CanDeleteItems(rows);
+        if (result)
         {
-            base.AfterLoad(editor, items);
-
-            EnsureAssignee();
-            EnsureAllocator();
+            result = !rows.Any(r => r.Get<KanbanSubscriber, bool>(c => c.Manager) || r.Get<KanbanSubscriber, bool>(c => c.Assignee));
+            if (!result)
+                MessageWindow.ShowMessage("You cannot delete an Assignee or Manager!", "Invalid operation");
         }
 
-        protected override bool CanDeleteItems(params CoreRow[] rows)
+        return result;
+    }
+
+    private bool SelectTeamClick(Button arg1, CoreRow[] arg2)
+    {
+        var dlg = new MultiSelectDialog<Team>(LookupFactory.DefineFilter<Team>(), Columns.None<Team>());
+        if (dlg.ShowDialog())
         {
-            var result = base.CanDeleteItems(rows);
-            if (result)
-            {
-                result = !rows.Any(r => r.Get<KanbanSubscriber, bool>(c => c.Manager) || r.Get<KanbanSubscriber, bool>(c => c.Assignee));
-                if (!result)
-                    MessageBox.Show("You cannot delete an Assignee or Manager!");
-            }
+            var items = LoadItems(Data.Rows).ToList();
 
-            return result;
-        }
+            var observers = items.Where(x => x.Observer)
+                .Select(x => x.Employee.ID)
+                .ToArray();
 
-        private bool SelectTeamClick(Button arg1, CoreRow[] arg2)
-        {
-            var dlg = new MultiSelectDialog<Team>(LookupFactory.DefineFilter<Team>(), LookupFactory.DefineColumns<Team>());
-            if (dlg.ShowDialog())
-            {
-                var observers = Items.Where(x => x.Observer).Select(x => x.Employee.ID).ToArray();
+            var mr = MessageWindowResult.No;
+            if (observers.Length != 0)
+                mr = MessageWindow.ShowYesNoCancel("Do you want to clear the existing subscriber list?", "Clear Subscribers", image: MessageWindow.QuestionImage);
+            if (mr == MessageWindowResult.Cancel) return false;
 
-                var mr = MessageBoxResult.No;
-                if (observers.Any())
-                    mr = MessageBox.Show("Do you want to clear the existing subscriber list?", "Clear Subscribers", MessageBoxButton.YesNoCancel,
-                        MessageBoxImage.Question);
-                if (mr == MessageBoxResult.Cancel) return false;
+            if (mr == MessageWindowResult.Yes)
+            {
+                Client.Delete(items.Where(x => !x.Manager && !x.Assignee), "Selected team.");
 
-                if (mr == MessageBoxResult.Yes)
-                {
-                    Items.RemoveAll(x => !x.Manager && !x.Assignee);
-                    foreach (var item in Items)
-                        item.Observer = false;
-                }
+                foreach (var item in items)
+                    item.Observer = false;
+            }
 
-                var emps = new Client<EmployeeTeam>().Query(
-                    new Filter<EmployeeTeam>(x => x.TeamLink.ID).InList(dlg.IDs()),
-                    Columns.None<EmployeeTeam>().Add(x => x.EmployeeLink.ID, x => x.EmployeeLink.Code, x => x.EmployeeLink.Name),
-                    new SortOrder<EmployeeTeam>(x => x.EmployeeLink.Name)
-                );
+            var emps = Client.Query(
+                new Filter<EmployeeTeam>(x => x.TeamLink.ID).InList(dlg.IDs()),
+                Columns.None<EmployeeTeam>().Add(x => x.EmployeeLink.ID, x => x.EmployeeLink.Code, x => x.EmployeeLink.Name),
+                new SortOrder<EmployeeTeam>(x => x.EmployeeLink.Name));
 
-                foreach (var row in emps.Rows)
+            foreach (var membership in emps.ToObjects<EmployeeTeam>())
+            {
+                var subscriber = items.FirstOrDefault(x => x.Employee.ID == membership.EmployeeLink.ID);
+                if (subscriber == null)
                 {
-                    var membership = row.ToObject<EmployeeTeam>();
-
-                    var subscriber = Items.FirstOrDefault(x => x.Employee.ID == membership.EmployeeLink.ID);
-                    if (subscriber == null)
-                    {
-                        subscriber = new KanbanSubscriber();
-                        Items.Add(subscriber);
-                    }
-
-                    subscriber.Employee.ID = membership.EmployeeLink.ID;
-                    subscriber.Employee.Synchronise(membership.EmployeeLink);
-                    subscriber.Observer = true;
+                    subscriber = new KanbanSubscriber();
+                    subscriber.Kanban.CopyFrom(Kanban);
+                    items.Add(subscriber);
                 }
 
-                return true;
+                subscriber.Employee.ID = membership.EmployeeLink.ID;
+                subscriber.Employee.Synchronise(membership.EmployeeLink);
+                subscriber.Observer = true;
             }
 
-            return false;
-        }
-
-        private void LoadEmployee(Guid empid, KanbanSubscriber subscriber)
-        {
-            var emp = new Client<Employee>().Load(new Filter<Employee>(x => x.ID).IsEqualTo(empid)).FirstOrDefault();
-            if (emp != null)
-                subscriber.Employee.Synchronise(emp);
-        }
-
-        private KanbanSubscriber? EnsureSubscriber(EmployeeLink employee)
-        {
-            if (!employee.IsValid()) return null;
-
-            var result = Items.FirstOrDefault(x => x.Employee.ID == employee.ID);
-            if (result is null)
-            {
-                result = new KanbanSubscriber();
-                result.Employee.ID = employee.ID;
-                LoadEmployee(employee.ID, result);
-                Items.Add(result);
-            }
+            Client.Save(items, "Selected team.");
 
-            return result;
+            return true;
         }
 
-        public void EnsureAssignee()
-        {
-            var subscriber = EnsureSubscriber(Item.EmployeeLink);
-            foreach (var item in Items)
-                item.Assignee = false;
-            if (subscriber != null)
-                subscriber.Assignee = true;
-            Items.RemoveAll(x => !x.Assignee && !x.Manager && !x.Observer);
-            Refresh(false, true);
-        }
+        return false;
+    }
 
-        public void EnsureAllocator()
-        {
-            var subscriber = EnsureSubscriber(Item.ManagerLink);
-            foreach (var item in Items)
-                item.Manager = false;
-            if (subscriber != null)
-                subscriber.Manager = true;
-            Items.RemoveAll(x => !x.Assignee && !x.Manager && !x.Observer);
-            Refresh(false, true);
-        }
+    protected override void Reload(Filters<KanbanSubscriber> criteria, Columns<KanbanSubscriber> columns, ref SortOrder<KanbanSubscriber>? sort, CancellationToken token, Action<CoreTable?, Exception?> action)
+    {
+        criteria.Add(new Filter<KanbanSubscriber>(x => x.Kanban.ID).IsEqualTo(Kanban.ID));
+        base.Reload(criteria, columns, ref sort, token, action);
     }
 }

+ 11 - 52
prs.desktop/Panels/Tasks/TaskGrid.cs

@@ -23,7 +23,6 @@ public class TaskGrid : DynamicDataGrid<Kanban>, ITaskControl, IDefaultGrid
         HiddenColumns.Add(x => x.EmployeeLink.ID);
 
         OnCustomiseEditor += CustomiseEditor;
-        OnBeforeSave += BeforeSave;
     }
 
     protected override void DoReconfigure(DynamicGridOptions options)
@@ -55,13 +54,23 @@ public class TaskGrid : DynamicDataGrid<Kanban>, ITaskControl, IDefaultGrid
 
         var menu = new ContextMenu();
 
-        DynamicGridUtils.PopulateFormMenu<KanbanForm, Kanban, KanbanLink>(menu, kanbanID, () => row.ToObject<Kanban>());
+        var digitalForms = menu.AddItem("Digital Forms", null, null);
+
+        DynamicGridUtils.PopulateFormMenu<KanbanForm, Kanban, KanbanLink>(digitalForms, kanbanID, () => row.ToObject<Kanban>());
+
+        menu.AddItem("Manage Subscribers", null, row, Subscribers_Click);
 
         menu.IsOpen = true;
 
         return false;
     }
 
+    private void Subscribers_Click(CoreRow row)
+    {
+        var kanban = row.ToObject<Kanban>();
+        TaskPanel.ManageSubscribers(kanban);
+    }
+
     private void CustomiseEditor(IDynamicEditorForm sender, Kanban[]? items, DynamicGridColumn column, BaseEditor editor)
     {
         var kanban = items?.FirstOrDefault();
@@ -110,23 +119,6 @@ public class TaskGrid : DynamicDataGrid<Kanban>, ITaskControl, IDefaultGrid
         return true;
     }
 
-    private void BeforeSave(IDynamicEditorForm editor, BaseObject[] items)
-    {
-        EnsureAssignee(editor, items.FirstOrDefault() as Kanban);
-        EnsureAllocator(editor, items.FirstOrDefault() as Kanban);
-    }
-
-    protected override void SelectPage(object sender, BaseObject[]? items)
-    {
-        base.SelectPage(sender, items);
-        var tab = sender as TabItem;
-        if(tab?.Content is KanbanSubscriberGrid grid)
-        {
-            grid.EnsureAssignee();
-            grid.EnsureAllocator();
-        }
-    }
-
     protected override Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, Kanban[] items, string name, object value)
     {
         var result = base.EditorValueChanged(editor, items, name, value);
@@ -138,14 +130,12 @@ public class TaskGrid : DynamicDataGrid<Kanban>, ITaskControl, IDefaultGrid
         }
         else if (name == "EmployeeLink.ID")
         {
-            EnsureAssignee(editor, items.FirstOrDefault());
             var enabled = items.FirstOrDefault()?.ManagerLink.UserLink.ID == ClientFactory.UserGuid &&
                           (Guid)(value ?? Guid.Empty) == items.FirstOrDefault()?.ManagerLink.ID;
             editor.FindEditor("Private").IsEnabled = enabled;
         }
         else if (name == "ManagerLink.ID")
         {
-            EnsureAllocator(editor, items.FirstOrDefault());
             var enabled = items.FirstOrDefault()?.EmployeeLink.UserLink.ID == ClientFactory.UserGuid &&
                           (Guid)(value ?? Guid.Empty) == items.FirstOrDefault()?.EmployeeLink.ID;
             editor.FindEditor("Private").IsEnabled = enabled;
@@ -173,37 +163,6 @@ public class TaskGrid : DynamicDataGrid<Kanban>, ITaskControl, IDefaultGrid
         return result;
     }
 
-    private static KanbanSubscriberGrid? EnsurePage(IDynamicEditorForm editor, Kanban item)
-    {
-        var type = typeof(IDynamicOneToManyGrid<,>).MakeGenericType(typeof(Kanban), typeof(KanbanSubscriber));
-        var page = editor.Pages.FirstOrDefault(x => x.GetType().GetInterfaces().Contains(type)) as KanbanSubscriberGrid;
-
-        if (page != null && !page.Ready)
-            page.Load(item, null);
-
-        return page;
-    }
-
-    private static void EnsureAssignee(IDynamicEditorForm editor, Kanban? kanban)
-    {
-        if (kanban is null) return;
-
-        var page = EnsurePage(editor, kanban);
-        if (page is null) return;
-
-        page.EnsureAssignee();
-    }
-
-    private static void EnsureAllocator(IDynamicEditorForm editor, Kanban? kanban)
-    {
-        if (kanban is null) return;
-
-        var page = EnsurePage(editor, kanban);
-        if (page is null) return;
-
-        page.EnsureAllocator();
-    }
-
     protected override void Reload(
     	Filters<Kanban> criteria, Columns<Kanban> columns, ref SortOrder<Kanban>? sort,
     	CancellationToken token, Action<CoreTable?, Exception?> action)

+ 38 - 33
prs.desktop/Panels/Tasks/TaskPanel.xaml.cs

@@ -180,32 +180,28 @@ public partial class TaskPanel : UserControl, IPanel<Kanban>, ITaskHost, IMaster
         var bSingle = models.Length == 1;
         var canChange = CanChangeTasks(models);
 
-        var edit = new MenuItem
-        {
-            Tag = models,
-            Header = referencetypes.SingleOrDefault() == typeof(Requisition)
-                ? "Edit Requisition Details"
-                : referencetypes.SingleOrDefault() == typeof(Setout)
-                    ? "Edit Setout Details"
-                    : referencetypes.SingleOrDefault() == typeof(Delivery)
-                        ? "Edit Delivery Details"
-                        : referencetypes.SingleOrDefault() == typeof(PurchaseOrder)
-                            ? "Edit Order Details"
-                            : "Edit Task" + (bSingle ? "" : "s")
-        };
-        edit.Click += (o, e) =>
-        {
-            var tasks = (((MenuItem)e.Source).Tag as IEnumerable<TaskModel>)!;
-            if (EditReferences(tasks))
-                control.Refresh();
-            e.Handled = true;
-        };
-        edit.IsEnabled = referencetypes.Length == 1;
-        menu.Items.Add(edit);
+        menu.AddItem(referencetypes.SingleOrDefault() == typeof(Requisition)
+            ? "Edit Requisition Details"
+            : referencetypes.SingleOrDefault() == typeof(Setout)
+                ? "Edit Setout Details"
+                : referencetypes.SingleOrDefault() == typeof(Delivery)
+                    ? "Edit Delivery Details"
+                    : referencetypes.SingleOrDefault() == typeof(PurchaseOrder)
+                        ? "Edit Order Details"
+                        : "Edit Task" + (bSingle ? "" : "s"),
+            null,
+            () =>
+            {
+                if (EditReferences(models))
+                {
+                    control.Refresh();
+                }
+            },
+            enabled: referencetypes.Length == 1);
 
         if (!bLinks && models.Length == 1)
         {
-            var digitalForms = new MenuItem { Header = "Digital Forms" };
+            var digitalForms = menu.AddItem("Digital Forms", null, null);
 
             var model = models.First();
 
@@ -215,24 +211,18 @@ public partial class TaskPanel : UserControl, IPanel<Kanban>, ITaskHost, IMaster
                 () => new Client<Kanban>().Load(new Filter<Kanban>(x => x.ID).IsEqualTo(model.ID)).First(),
                 model.EmployeeID == App.EmployeeID);
 
-            menu.Items.Add(digitalForms);
+            menu.AddItem("Manage Subscribers", null, model, Subscribers_Click);
         }
 
         if (!models.Any(x => !x.CompletedDate.IsEmpty()) && !bLinks)
         {
-            menu.Items.Add(new Separator());
+            menu.AddSeparatorIfNeeded();
 
-            var job = new MenuItem
-            {
-                Tag = models,
-                Header = "Link to Job"
-            };
+            var job = menu.AddItem("Link to Job", null, null);
             job.SubmenuOpened += (o, e) => CreateJobSubMenu(control, job, models);
-            menu.Items.Add(job);
 
             if (bSingle)
             {
-
                 menu.AddItem("Create Setout from Task", null, models.First(), task =>
                 {
                     if (!MessageWindow.ShowYesNo("This will convert this task into a Setout.\n\nDo you wish to continue?", "Confirmation"))
@@ -475,7 +465,7 @@ public partial class TaskPanel : UserControl, IPanel<Kanban>, ITaskHost, IMaster
 
         if (!bLinks && canChange)
         {
-            menu.Items.Add(new Separator());
+            menu.AddSeparatorIfNeeded();
 
             var changeStatus = new MenuItem { Header = "Change Status" };
 
@@ -604,6 +594,21 @@ public partial class TaskPanel : UserControl, IPanel<Kanban>, ITaskHost, IMaster
         }
     }
 
+    private void Subscribers_Click(TaskModel model)
+    {
+        var kanban = LoadKanbans([model], Columns.Required<Kanban>().Add(x => x.ID).Add(x => x.Number)).First();
+        ManageSubscribers(kanban);
+    }
+
+    public static void ManageSubscribers(Kanban kanban)
+    {
+        var grid = new KanbanSubscriberGrid(kanban);
+        var window = DynamicGridUtils.CreateGridWindow($"Manage subscribers for Task #{kanban.Number}", grid);
+        window.Width = 400;
+        window.Height = 400;
+        window.ShowDialog();
+    }
+
     /// <summary>
     /// Takes a <see cref="KanbanDocument"/>, and if it is a .txt or an image (".png", ".jpg", ".jpeg" or ".bmp"), converts to a PDF
     /// with the content of the document, saving a new document with extension changed to ".pdf".

+ 162 - 167
prs.stores/KanbanStore.cs

@@ -8,206 +8,201 @@ using InABox.Core;
 using InABox.Database;
 using System;
 
-namespace Comal.Stores
+namespace Comal.Stores;
+
+public class ServerKanbanSubscriberSet(IEnumerable<Guid> kanbanids, IProvider provider) : BaseKanbanSubscriberSet(kanbanids)
 {
-    public class ServerKanbanSubscriberSet : BaseKanbanSubscriberSet
-    {
-        public ServerKanbanSubscriberSet(IEnumerable<Guid> kanbanids) : base(kanbanids)
-        {
-            
-        }
-        
-        protected override void DoSave(IEnumerable<KanbanSubscriber> updates)
-            => DbFactory.NewProvider(Logger.Main).Save(updates);
+    protected override void DoSave(IEnumerable<KanbanSubscriber> updates)
+        => provider.Save(updates);
 
-        protected override void DoDelete(KanbanSubscriber[] deletes)
-            => DbFactory.NewProvider(Logger.Main).Delete(deletes,"");
+    protected override void DoDelete(KanbanSubscriber[] deletes)
+        => provider.Delete(deletes,"");
 
-        protected override CoreTable DoQuery(Filter<KanbanSubscriber> filter, Columns<KanbanSubscriber> columns)
-            => DbFactory.NewProvider(Logger.Main).Query(filter, columns);
-        
-    }
+    protected override CoreTable DoQuery(Filter<KanbanSubscriber> filter, Columns<KanbanSubscriber> columns)
+        => provider.Query(filter, columns);
     
-    internal class KanbanStore : ScheduleActionStore<Kanban>
+}
+
+internal class KanbanStore : ScheduleActionStore<Kanban>
+{
+    private void CheckNotifications(Kanban entity)
     {
-        private void CheckNotifications(Kanban entity)
+        if (entity.HasOriginalValue(x => x.Completed) && !entity.Completed.IsEmpty())
         {
-            if (entity.HasOriginalValue(x => x.Completed) && !entity.Completed.IsEmpty())
-            {
-                // Find all notifications linked to this task
-                var notifications = Provider.Query(
-                    new Filter<Notification>(x => x.EntityType).IsEqualTo(typeof(Kanban).EntityName()).And(x => x.EntityID).IsEqualTo(entity.ID)
-                        .And(x => x.Closed).IsEqualTo(DateTime.MinValue),
-                    Columns.None<Notification>().Add(x => x.ID)
-                ).Rows.Select(x => x.ToObject<Notification>()).ToArray();
-                foreach (var notification in notifications)
-                    notification.Closed = entity.Completed;
-                if (notifications.Any())
-                    FindSubStore<Notification>().Save(notifications, "");
-            }
+            // Find all notifications linked to this task
+            var notifications = Provider.Query(
+                new Filter<Notification>(x => x.EntityType).IsEqualTo(typeof(Kanban).EntityName())
+                    .And(x => x.EntityID).IsEqualTo(entity.ID)
+                    .And(x => x.Closed).IsEqualTo(DateTime.MinValue),
+                Columns.None<Notification>().Add(x => x.ID)
+            ).ToArray<Notification>();
+            foreach (var notification in notifications)
+                notification.Closed = entity.Completed;
+            if (notifications.Any())
+                FindSubStore<Notification>().Save(notifications, "");
         }
+    }
 
-        private void NotifyUsers(Kanban entity)
+    private void NotifyUsers(Kanban entity)
+    {
+        var Subject = $"Updated Task: {entity.Title} (Due: {entity.DueDate:dd MMM yy})";        
+        var emps = Provider.Query(
+            new Filter<KanbanSubscriber>(x => x.Kanban.ID).IsEqualTo(entity.ID),
+            Columns.None<KanbanSubscriber>().Add(x => x.Employee.ID, x => x.Employee.UserLink.ID));          
+        var senderrow = emps.Rows.FirstOrDefault(r => r.Get<KanbanSubscriber, Guid>(c => c.Employee.UserLink.ID).Equals(UserGuid));
+        var senderid = senderrow?.Get<KanbanSubscriber, Guid>(c => c.Employee.ID) ?? Guid.Empty;
+        var notifications = new List<Notification>();
+        foreach (var row in emps.Rows)
         {
-            var Subject = $"Updated Task: {entity.Title} (Due: {entity.DueDate:dd MMM yy})";        
-            var emps = Provider.Query(
-                new Filter<KanbanSubscriber>(x => x.Kanban.ID).IsEqualTo(entity.ID),
-                Columns.None<KanbanSubscriber>().Add(x => x.Employee.ID, x => x.Employee.UserLink.ID));          
-            var senderrow = emps.Rows.FirstOrDefault(r => r.Get<KanbanSubscriber, Guid>(c => c.Employee.UserLink.ID).Equals(UserGuid));
-            var senderid = senderrow?.Get<KanbanSubscriber, Guid>(c => c.Employee.ID) ?? Guid.Empty;
-            var notifications = new List<Notification>();
-            foreach (var row in emps.Rows)
+            var userid = row.Get<KanbanSubscriber, Guid>(c => c.Employee.UserLink.ID);
+            if (userid != UserGuid)
             {
-                var userid = row.Get<KanbanSubscriber, Guid>(c => c.Employee.UserLink.ID);
-                if (userid != UserGuid)
-                {
-                    var notification = new Notification();
-                    notification.Employee.ID = row.Get<KanbanSubscriber, Guid>(c => c.Employee.ID);
-                    notification.Sender.ID = senderid;
-                    notification.Title = Subject;
-                    notification.EntityType = typeof(Kanban).EntityName();
-                    notification.EntityID = entity.ID;
-                    notification.Description = BuildDescription(entity);
-                    notifications.Add(notification);
-                }
+                var notification = new Notification();
+                notification.Employee.ID = row.Get<KanbanSubscriber, Guid>(c => c.Employee.ID);
+                notification.Sender.ID = senderid;
+                notification.Title = Subject;
+                notification.EntityType = typeof(Kanban).EntityName();
+                notification.EntityID = entity.ID;
+                notification.Description = BuildDescription(entity);
+                notifications.Add(notification);
             }
-            if (emps.Rows.Count == 0)
+        }
+        if (emps.Rows.Count == 0)
+        {
+            var userID = Provider.Query(
+                new Filter<Employee>(x => x.ID).IsEqualTo(entity.EmployeeLink.ID),
+                Columns.None<Employee>().Add(x => x.UserLink.ID)
+                ).Rows.FirstOrDefault()?.Get<Employee, Guid>(x => x.UserLink.ID);
+            if(userID != UserGuid)
             {
-                var userID = Provider.Query(
-                    new Filter<Employee>(x => x.ID).IsEqualTo(entity.EmployeeLink.ID),
-                    Columns.None<Employee>().Add(x => x.UserLink.ID)
-                    ).Rows.FirstOrDefault()?.Get<Employee, Guid>(x => x.UserLink.ID);
-                if(userID != UserGuid)
-                {
-                    Notification notification = new Notification();
-                    notification.Employee.ID = entity.EmployeeLink.ID;
-                    notification.Sender.ID = entity.ManagerLink.ID;
-                    notification.Title = Subject;
-                    notification.EntityType = typeof(Kanban).EntityName();
-                    notification.EntityID = entity.ID;
-                    notification.Description = BuildDescription(entity);
-                    notifications.Add(notification);
-                }
+                Notification notification = new Notification();
+                notification.Employee.ID = entity.EmployeeLink.ID;
+                notification.Sender.ID = entity.ManagerLink.ID;
+                notification.Title = Subject;
+                notification.EntityType = typeof(Kanban).EntityName();
+                notification.EntityID = entity.ID;
+                notification.Description = BuildDescription(entity);
+                notifications.Add(notification);
             }
-            if (notifications.Any())
-                FindSubStore<Notification>().Save(notifications, "");
         }
+        if (notifications.Any())
+            FindSubStore<Notification>().Save(notifications, "");
+    }
+
+    private string BuildDescription(Kanban entity)
+    {
+        var notes = string.Join("\r\n", entity.Notes)
+            .Split(new[] { "===================================\r\n" }, StringSplitOptions.RemoveEmptyEntries);
+        var summary = notes.LastOrDefault();
+        if (string.IsNullOrEmpty(summary))
+            summary = entity.Summary;
+        if (string.IsNullOrEmpty(summary))
+            summary = "";
+        summary = Regex.Replace(summary.Replace("\r", "\n"), @"( |\r?\n|\r|\n)\1+", "$1");
+
+        var sb = new StringBuilder();
+        if (entity.EmployeeLink.HasOriginalValue(x => x.ID))
+            sb.AppendLine("The above task has been assigned to you. Please check the relevant details, and action as required.");
+        else
+            sb.AppendLine("The above task has been changed.  Please check the relevant details, and action as required.");
 
-        private string BuildDescription(Kanban entity)
+        if (entity.HasOriginalValue(x => x.Status))
         {
-            var notes = string.Join("\r\n", entity.Notes)
-                .Split(new[] { "===================================\r\n" }, StringSplitOptions.RemoveEmptyEntries);
-            var summary = notes.LastOrDefault();
-            if (string.IsNullOrEmpty(summary))
-                summary = entity.Summary;
-            if (string.IsNullOrEmpty(summary))
-                summary = "";
-            summary = Regex.Replace(summary.Replace("\r", "\n"), @"( |\r?\n|\r|\n)\1+", "$1");
-
-            var sb = new StringBuilder();
-            if (entity.EmployeeLink.HasOriginalValue(x => x.ID))
-                sb.AppendLine("The above task has been assigned to you. Please check the relevant details, and action as required.");
-            else
-                sb.AppendLine("The above task has been changed.  Please check the relevant details, and action as required.");
-
-            if (entity.HasOriginalValue(x => x.Status))
-            {
-                sb.AppendLine();
-                sb.AppendLine(string.Format("- The task has progressed from {0} to {1}.", entity.GetOriginalValue(x => x.Status),
-                    entity.Status));
-            }
-            if (entity.HasOriginalValue(x => x.DueDate) && entity.GetOriginalValue(x => x.DueDate) != DateTime.MinValue)
-            {
-                sb.AppendLine();
-                sb.AppendLine(string.Format("- The due date for the task has been changed from {0} to {1}.",
-                    entity.OriginalValueList["DueDate"],
-                    entity.DueDate));
-            }
-            if (entity.HasOriginalValue(x => x.Notes) && entity.Notes.Count() > 1)
-            {
-                sb.AppendLine("- The notes for the task have been updated as follows:");
-                foreach (var line in summary.Split('\n'))
-                    sb.AppendLine("    " + line);
-            }
-            return sb.ToString();
+            sb.AppendLine();
+            sb.AppendLine(string.Format("- The task has progressed from {0} to {1}.", entity.GetOriginalValue(x => x.Status),
+                entity.Status));
         }
+        if (entity.HasOriginalValue(x => x.DueDate) && entity.GetOriginalValue(x => x.DueDate) != DateTime.MinValue)
+        {
+            sb.AppendLine();
+            sb.AppendLine(string.Format("- The due date for the task has been changed from {0} to {1}.",
+                entity.OriginalValueList["DueDate"],
+                entity.DueDate));
+        }
+        if (entity.HasOriginalValue(x => x.Notes) && entity.Notes.Count() > 1)
+        {
+            sb.AppendLine("- The notes for the task have been updated as follows:");
+            foreach (var line in summary.Split('\n'))
+                sb.AppendLine("    " + line);
+        }
+        return sb.ToString();
+    }
 
-        private void CheckNotificationRequired(Kanban entity)
+    private void CheckNotificationRequired(Kanban entity)
+    {
+        if (entity.EmployeeLink.HasOriginalValue(x => x.ID) || entity.HasOriginalValue(x => x.Notes) ||
+                entity.HasOriginalValue(x => x.Status) ||
+                entity.HasOriginalValue(x => x.Summary) || entity.HasOriginalValue(x => x.DueDate))
         {
-            if (entity.EmployeeLink.HasOriginalValue(x => x.ID) || entity.HasOriginalValue(x => x.Notes) ||
-                    entity.HasOriginalValue(x => x.Status) ||
-                    entity.HasOriginalValue(x => x.Summary) || entity.HasOriginalValue(x => x.DueDate))
+            NotifyUsers(entity);
+        }
+        else if (entity.ID != Guid.Empty && entity.HasOriginalValue(x => x.ID))
+        {
+            if (entity.GetOriginalValue<Kanban, Guid>("ID") == Guid.Empty)
             {
                 NotifyUsers(entity);
             }
-            else if (entity.ID != Guid.Empty && entity.HasOriginalValue(x => x.ID))
-            {
-                if (entity.GetOriginalValue<Kanban, Guid>("ID") == Guid.Empty)
-                {
-                    NotifyUsers(entity);
-                }
-            }
         }
+    }
 
-        protected override void BeforeSave(Kanban entity)
-        {
-            base.BeforeSave(entity);
-            if (entity.Completed.IsEmpty())
-                entity.Closed = DateTime.MinValue;
-        }
+    protected override void BeforeSave(Kanban entity)
+    {
+        base.BeforeSave(entity);
+        if (entity.Completed.IsEmpty())
+            entity.Closed = DateTime.MinValue;
+    }
 
-        private void CheckKanbanTypeForms(Kanban kanban)
+    private void CheckKanbanTypeForms(Kanban kanban)
+    {
+        if(!kanban.Type.HasOriginalValue(x => x.ID))
         {
-            if(!kanban.Type.HasOriginalValue(x => x.ID))
-            {
-                return;
-            }
-            var kanbanForms = Provider.Query(
-                new Filter<KanbanForm>(x => x.Parent.ID).IsEqualTo(kanban.ID)).Rows.Select(x => x.ToObject<KanbanForm>()).ToArray();
-            var toDelete = kanbanForms.Where(x => string.IsNullOrWhiteSpace(x.FormData)).ToArray();
-            Provider.Delete(toDelete, UserID);
+            return;
+        }
+        var kanbanForms = Provider.Query(
+            new Filter<KanbanForm>(x => x.Parent.ID).IsEqualTo(kanban.ID)).Rows.Select(x => x.ToObject<KanbanForm>()).ToArray();
+        var toDelete = kanbanForms.Where(x => string.IsNullOrWhiteSpace(x.FormData)).ToArray();
+        Provider.Delete(toDelete, UserID);
 
-            var kanbanTypeForms = Provider.Query(
-                new Filter<KanbanTypeForm>(x => x.Type.ID).IsEqualTo(kanban.Type.ID)
-                    .And(x => x.Form.AppliesTo).IsEqualTo(nameof(Kanban)));
+        var kanbanTypeForms = Provider.Query(
+            new Filter<KanbanTypeForm>(x => x.Type.ID).IsEqualTo(kanban.Type.ID)
+                .And(x => x.Form.AppliesTo).IsEqualTo(nameof(Kanban)));
 
-            var newForms = new List<KanbanForm>();
-            foreach(var row in kanbanTypeForms.Rows)
+        var newForms = new List<KanbanForm>();
+        foreach(var row in kanbanTypeForms.Rows)
+        {
+            var formID = row.Get<KanbanTypeForm, Guid>(x => x.Form.ID);
+            if (!(kanbanForms.Any(x => x.Form.ID == formID) && toDelete.All(x => x.Form.ID != formID)))
             {
-                var formID = row.Get<KanbanTypeForm, Guid>(x => x.Form.ID);
-                if (!(kanbanForms.Any(x => x.Form.ID == formID) && toDelete.All(x => x.Form.ID != formID)))
-                {
-                    var kanbanForm = new KanbanForm();
-                    kanbanForm.Form.ID = formID;
-                    kanbanForm.Parent.ID = kanban.ID;
-                    newForms.Add(kanbanForm);
-                }
+                var kanbanForm = new KanbanForm();
+                kanbanForm.Form.ID = formID;
+                kanbanForm.Parent.ID = kanban.ID;
+                newForms.Add(kanbanForm);
             }
-            Provider.Save(newForms);
         }
+        Provider.Save(newForms);
+    }
+
+    protected override void AfterSave(Kanban entity)
+    {
+        base.AfterSave(entity);
+        CheckNotifications(entity);
+        CheckNotificationRequired(entity);
+        CheckKanbanTypeForms(entity);
+        CheckSubscribers(entity);
+    }
 
-        protected override void AfterSave(Kanban entity)
+    private void CheckSubscribers(Kanban entity)
+    {
+        var changedEmployee = entity.HasOriginalValue(x => x.EmployeeLink.ID);
+        var changedManager = entity.HasOriginalValue(x => x.ManagerLink.ID);
+        if (changedEmployee || changedManager)
         {
-            base.AfterSave(entity);
-            CheckNotifications(entity);
-            CheckNotificationRequired(entity);
-            CheckKanbanTypeForms(entity);
-            //CheckSubscribers(entity);
+            var set = new ServerKanbanSubscriberSet([entity.ID], Provider);
+            if (changedEmployee)
+                set.EnsureAssignee(entity.ID, entity.EmployeeLink.ID);
+            if (changedManager)
+                set.EnsureManager(entity.ID, entity.ManagerLink.ID);
+            set.Save();
         }
-
-        // private void CheckSubscribers(Kanban entity)
-        // {
-        //     if (entity.EmployeeLink.HasOriginalValue(x => x.ID) || (entity.ManagerLink.HasOriginalValue(x => x.ID)))
-        //     {
-        //         var set = new ServerKanbanSubscriberSet(new Guid[] { entity.ID });
-        //         if (entity.EmployeeLink.HasOriginalValue(x => x.ID))
-        //             set.EnsureAssignee(entity.ID,entity.EmployeeLink.ID);
-        //         if (entity.ManagerLink.HasOriginalValue(x => x.ID))
-        //             set.EnsureManager(entity.ID,entity.ManagerLink.ID);
-        //         set.Save();
-        //
-        //     }
-        // }
-        
     }
 }