Browse Source

Improved Submenu handling on Ribbon Buttons
Added ability to change Job and Job Scope on Digital Forms Dashboard and Dock

frogsoftware 10 months ago
parent
commit
98eaf8626d

+ 1 - 1
prs.classes/Entities/Assignment/Assignment.cs

@@ -61,7 +61,7 @@ namespace Comal.Classes
 
         [EditorSequence(9)]
         public JobLink JobLink { get; set; }
-
+        
         private class JobITPLookup : LookupDefinitionGenerator<JobITP, Assignment>
         {
             public override Filter<JobITP> DefineFilter(Assignment[] items)

+ 3 - 0
prs.classes/Entities/Assignment/AssignmentLink.cs

@@ -46,5 +46,8 @@ namespace Comal.Classes
 
         [NullEditor]
         public JobLink JobLink { get; set; }
+        
+        [NullEditor]
+        public JobScopeLink JobScope { get; set; }
     }
 }

+ 1 - 0
prs.desktop/Dashboards/Common/DigitalFormsDashboard.xaml

@@ -31,6 +31,7 @@
                     Margin="0,5,5,5"
                     CellDoubleTapped="DataGrid_CellDoubleTapped"
                     CellTapped="DataGrid_CellTapped"
+                    SelectionChanged="DataGrid_SelectionChanged"
                     FilterRowPosition="FixedTop">
         </syncfusion:SfDataGrid>
 

+ 268 - 45
prs.desktop/Dashboards/Common/DigitalFormsDashboard.xaml.cs

@@ -44,8 +44,10 @@ using Image = System.Windows.Controls.Image;
 using Microsoft.Win32;
 using Syncfusion.CompoundFile.DocIO.Net;
 using System.IO;
+using com.healthmarketscience.jackcess.query;
 using sun.security.krb5.@internal;
 using Columns = InABox.Core.Columns;
+using PanelAction = InABox.Wpf.PanelAction;
 
 namespace PRSDesktop;
 
@@ -577,13 +579,14 @@ public partial class DigitalFormsDashboard : UserControl,
             }
         }
 
-        var jobLink = FormType is not null ? GetJobLink("", FormType) : "";
+        var jobLink = FormType is not null ? GetJobLink("", FormType,true) : "";
         if (string.IsNullOrWhiteSpace(jobLink))
         {
             var jobID = Properties.JobID;
             JobBox.SelectedValue = Guid.Empty;
             JobBox.IsEnabled = false;
             Properties.JobID = jobID;
+            
         }
         else
         {
@@ -591,6 +594,20 @@ public partial class DigitalFormsDashboard : UserControl,
             JobBox.IsEnabled = true;
         }
 
+        string changeableJobLink = "";
+        string changeableScopeLink = "";
+        if (changeableLinks.TryGetValue(FormType, out Tuple<string,string> _tuple))
+        {
+            changeableJobLink = _tuple.Item1;
+            changeableScopeLink = _tuple.Item2;
+        }
+        
+        if (_jobAction != null)
+            _jobAction.Visibility = string.IsNullOrWhiteSpace(changeableJobLink) ? Visibility.Collapsed : Visibility.Visible;
+        
+        if (_jobScopeAction != null)
+            _jobScopeAction.Visibility = string.IsNullOrWhiteSpace(changeableScopeLink) ? Visibility.Collapsed : Visibility.Visible;
+
         if (ParentType is null)
         {
             FormBox.IsEnabled = false;
@@ -653,9 +670,129 @@ public partial class DigitalFormsDashboard : UserControl,
 
     public event DataModelUpdateEvent? OnUpdateDataModel;
 
+    private PanelAction? _jobAction = null;
+    private PanelAction? _jobScopeAction = null;
     public void CreateToolbarButtons(IPanelHost host)
     {
         host.CreatePanelAction(new PanelAction("Export Forms", PRSDesktop.Resources.disk, action => SaveToFolder_Click()));
+
+        _jobAction ??= new PanelAction("Set Job Number", PRSDesktop.Resources.project, SetJobNumber);
+        host.CreatePanelAction(_jobAction);
+
+        _jobScopeAction ??= new PanelAction(
+            "Set Job Scope",
+            PRSDesktop.Resources.project,
+            null,
+            LoadJobScopes
+        );
+        host.CreatePanelAction(_jobScopeAction);
+        
+    }
+
+    private IPanelActionEntry[] LoadJobScopes(PanelAction arg)
+    {
+        List<IPanelActionEntry> result = new();
+        var linkcolumn = GetJobLink("", FormType, true);
+        if (!string.IsNullOrWhiteSpace(linkcolumn))
+        {
+            linkcolumn = $"{linkcolumn.Replace(".","_")}_ID";
+            var jobid = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row[linkcolumn]).FirstOrDefault();
+            if (jobid != null && !Guid.Equals(jobid, Guid.Empty))
+            {
+                var scopes =Client.Query<JobScope>(
+                    new Filter<JobScope>(x => x.Job.ID).IsEqualTo((Guid)jobid),
+                    Columns.None<JobScope>()
+                        .Add(x => x.ID)
+                        .Add(x => x.Number)
+                        .Add(x => x.Description)
+                ).ToObjects<JobScope>();
+                foreach (var scope in scopes)
+                {
+                    var entry = new PanelActionEntry<Guid>($"{scope.Number}: {scope.Description}",
+                        PRSDesktop.Resources.project, scope.ID, SetJobScope);
+                    result.Add(entry);
+                }
+            }
+        }
+
+        return result.ToArray();
+    }
+
+    private void SetJobScope(PanelActionEntry<Guid> scopeid)
+    {
+        Type? _type = FormType == typeof(JobForm)
+            ? FormType
+            : ParentType;
+
+        if (_type == null)
+            return;
+
+        string _idCol = FormType == typeof(JobForm)
+            ? "ID"
+            : "Parent_ID";
+            
+        Guid[] _ids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row[_idCol]).Distinct()
+            .OfType<Guid>().ToArray();
+            
+        Progress.ShowModal("Updating Job Scopes", progress =>
+        {
+                
+            var _updates = ClientFactory.CreateClient(_type)
+                .Query(
+                    Filter.Create(_type, "ID", Operator.InList, _ids),
+                    Columns.Create(_type, ColumnTypeFlags.Required)
+                ).ToObjects(_type).ToArray();
+            var _scopelink = GetJobScopeLink("", _type, FormType == typeof(JobForm) ? 0 : 1);
+            foreach (var _update in _updates)
+                CoreUtils.SetPropertyValue(_update, _scopelink + ".ID", scopeid.Data);
+            ClientFactory.CreateClient(_type).Save(_updates, "Updated Job Number");
+        });
+        Refresh();
+    }
+
+    private void SetJobNumber(PanelAction obj)
+    {
+        var dlg = new MultiSelectDialog<Job>(
+            LookupFactory.DefineFilter<Job>(),
+            Columns.None<Job>()
+                .Add(x => x.ID)
+                .Add(x => x.JobNumber)
+                .Add(x => x.Name)
+            , false
+        );
+        if (dlg.ShowDialog())
+        {
+            Guid _jobID = dlg.IDs().FirstOrDefault();
+            
+            Type? _type = FormType == typeof(JobForm)
+                ? FormType
+                : ParentType;
+
+            if (_type == null)
+                return;
+
+            string _idCol = FormType == typeof(JobForm)
+                ? "ID"
+                : "Parent_ID";
+            
+            Guid[] _ids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row[_idCol]).Distinct()
+                .OfType<Guid>().ToArray();
+            
+            Progress.ShowModal("Updating Job Numbers", progress =>
+            {
+                
+                var _updates = ClientFactory.CreateClient(_type)
+                    .Query(
+                        Filter.Create(_type, "ID", Operator.InList, _ids),
+                        Columns.Create(_type, ColumnTypeFlags.Required)
+                    ).ToObjects(_type).ToArray();
+                var _joblink = GetJobLink("", _type, false);
+                foreach (var _update in _updates)
+                    CoreUtils.SetPropertyValue(_update, _joblink + ".ID", _jobID);
+                ClientFactory.CreateClient(_type).Save(_updates, "Updated Job Number");
+            });
+            Refresh();
+        }
     }
 
     public void Heartbeat(TimeSpan time)
@@ -1039,15 +1176,19 @@ public partial class DigitalFormsDashboard : UserControl,
     private static readonly Dictionary<Type, List<Tuple<string, string>>> parentColumns = new()
     {
         { typeof(Kanban), new() { new("Parent.Number", "Task No") } },
-        { typeof(Job), new() { new("Parent.JobNumber", "Job No") } },
         { typeof(JobITP), new() { new("Parent.Code", "Code") } },
         { typeof(Assignment), new() { new("Parent.Number", "Ass. No") } },
-        { typeof(TimeSheet), new() { } },
-        { typeof(LeaveRequest), new() { } },
         { typeof(Employee), new() { new("Parent.Code", "Employee") } },
-        { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } },
+        { typeof(PurchaseOrderItem), new() { new("Parent.PONumber", "PO No") } }
     };
 
+    private static Dictionary<Type, Tuple<string, string>> changeableLinks = new()
+    {
+        { typeof(JobForm), new( "Parent.ID", "Scope.ID") },
+        { typeof(AssignmentForm), new( "JobLink.ID", "JobScope.ID") },
+        { typeof(KanbanForm), new( "JobLink.ID", "") }
+    };
+    
     private static bool CategoryToType(string category, [NotNullWhen(true)] out Type? formType, [NotNullWhen(true)] out Type? parentType)
     {
         if (FormInstanceTypes == null)
@@ -1105,7 +1246,7 @@ public partial class DigitalFormsDashboard : UserControl,
 
     #endregion
 
-    private string GetJobLink(string prefix, Type type)
+    private string GetJobLink(string prefix, Type type, bool recursive)
     {
         var props = type.GetProperties().Where(x =>
             x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType &&
@@ -1114,9 +1255,34 @@ public partial class DigitalFormsDashboard : UserControl,
         {
             if (prop.PropertyType == typeof(JobLink))
                 return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name;
-            var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name, prop.PropertyType);
-            if (!string.IsNullOrEmpty(result))
-                return result;
+            if (recursive)
+            {
+                var result = GetJobLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name,
+                    prop.PropertyType, recursive);
+                if (!string.IsNullOrEmpty(result))
+                    return result;
+            }
+        }
+
+        return "";
+    }
+    
+    private string GetJobScopeLink(string prefix, Type type, int recurselevel)
+    {
+        var props = type.GetProperties().Where(x =>
+            x.PropertyType.BaseType != null && x.PropertyType.BaseType.IsGenericType &&
+            x.PropertyType.BaseType.GetGenericTypeDefinition() == typeof(EntityLink<>));
+        foreach (var prop in props)
+        {
+            if (prop.PropertyType == typeof(JobScopeLink))
+                return (string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name;
+            if (recurselevel > 0)
+            {
+                var result = GetJobScopeLink((string.IsNullOrEmpty(prefix) ? "" : prefix + ".") + prop.Name,
+                    prop.PropertyType, recurselevel - 1);
+                if (!string.IsNullOrEmpty(result))
+                    return result;
+            }
         }
 
         return "";
@@ -1126,15 +1292,19 @@ public partial class DigitalFormsDashboard : UserControl,
     /// Find a link from the form type to an associated <see cref="Job"/>, allowing us to filter based on jobs.
     /// </summary>
     /// <returns>The property name of the <see cref="JobLink"/>.</returns>
-    private string GetJobLink<T>() where T : IDigitalFormInstance
-        => GetJobLink("", typeof(T));
+    private string GetJobLink<T>(bool recursive) where T : IDigitalFormInstance
+        => GetJobLink("", typeof(T), recursive);
+    
+    private string GetJobScopeLink<T>(int recurselevel) where T : IDigitalFormInstance
+        => GetJobScopeLink("", typeof(T), recurselevel);
 
     private IKeyedQueryDef GetFormQuery<T>()
         where T : Entity, IRemotable, IPersistent, IDigitalFormInstance, new()
     {
         var sort = LookupFactory.DefineSort<T>();
 
-        var jobLink = GetJobLink<T>();
+        var jobLink = GetJobLink<T>(true);
+        var jobScopeLink = GetJobScopeLink<T>(typeof(T) == typeof(JobForm) ? 0 : 1);
 
         var completedFilter = new Filter<T>(x => x.FormCompleted).IsGreaterThanOrEqualTo(From)
             .And(x => x.FormCompleted).IsLessThan(To.AddDays(1));
@@ -1179,13 +1349,20 @@ public partial class DigitalFormsDashboard : UserControl,
         if (parentColumns.TryGetValue(ParentType!, out var pColumns))
         {
             foreach (var (field, name) in pColumns)
-            {
                 columns.Add(field);
-            }
         }
 
         if (!string.IsNullOrWhiteSpace(jobLink))
+        {
             columns.Add(jobLink + ".ID");
+            columns.Add(jobLink + ".JobNumber");
+        }
+
+        if (!string.IsNullOrWhiteSpace(jobScopeLink))
+        {
+            columns.Add(jobScopeLink + ".ID");
+            columns.Add(jobScopeLink + ".Number");
+        }
 
         return new KeyedQueryDef<T>(filter, columns, sort);
     }
@@ -1202,21 +1379,33 @@ public partial class DigitalFormsDashboard : UserControl,
         data.Columns.Add("Location_Latitude", typeof(double));
         data.Columns.Add("Location_Longitude", typeof(double));
         data.Columns.Add("FormData", typeof(string));
+        
         data.Columns.Add("Number", typeof(string));
-        data.Columns.Add("Description", typeof(string));
+
+        if (parentColumns.TryGetValue(ParentType!, out var pColumns))
+        {
+            foreach (var (field, name) in pColumns)
+                data.Columns.Add(field.Replace(".","_"), typeof(string));
+        }
         
-        if (ParentType == typeof(JobITP))
+        var jobLink = GetJobLink("",FormType!,true);
+        if (!string.IsNullOrWhiteSpace(jobLink))
         {
-            data.Columns.Add("Job No", typeof(string));
+            if (!string.Equals(jobLink,"Parent"))
+                data.Columns.Add(jobLink.Replace(".","_") + "_ID", typeof(Guid));
+            data.Columns.Add(jobLink.Replace(".","_") + "_JobNumber", typeof(string));
         }
-        if (parentColumns.TryGetValue(ParentType!, out var pColumns))
+
+        var jobScopeLink = GetJobScopeLink("",FormType!,FormType == typeof(JobForm) ? 0 : 1);
+        if (!string.IsNullOrWhiteSpace(jobScopeLink))
         {
-            foreach (var (field, name) in pColumns)
-            {
-                data.Columns.Add(name, typeof(string));
-            }
+            data.Columns.Add(jobScopeLink.Replace(".","_") + "_ID", typeof(Guid));
+            data.Columns.Add(jobScopeLink.Replace(".","_") + "_Number", typeof(string));
         }
+        
 
+        data.Columns.Add("Description", typeof(string));
+        
         data.Columns.Add("Parent_Description", typeof(string));
         data.Columns.Add("Created", typeof(DateTime));
         data.Columns.Add("Created By", typeof(string));
@@ -1280,7 +1469,6 @@ public partial class DigitalFormsDashboard : UserControl,
                 dataRow["Number"] = form.Number;
                 dataRow["Description"] = form.Description;
                 
-
                 var desc = new List<string>();
                 foreach (var col in additionalColumns)
                 {
@@ -1300,26 +1488,35 @@ public partial class DigitalFormsDashboard : UserControl,
 
                 if (IsEntityForm)
                     dataRow["Processed"] = form.FormProcessed > DateTime.MinValue;
+                
+                
+                // if (ParentType == typeof(JobITP))
+                // {
+                //     var jobITP = jobITPs!.Rows.FirstOrDefault(x => x.Get<JobITP, Guid>(x => x.ID) == form.ParentID());
+                //     if (jobITP is not null)
+                //     {
+                //         var jobID = jobITP.Get<JobITP, Guid>(x => x.Job.ID);
+                //         dataRow["Job No"] = Jobs.Where(x => x.ID == jobID).FirstOrDefault()?.JobNumber;
+                //     }
+                // }
 
-                if (ParentType == typeof(JobITP))
+                if (pColumns != null)
                 {
-                    var jobITP = jobITPs!.Rows.FirstOrDefault(x => x.Get<JobITP, Guid>(x => x.ID) == form.ParentID());
-                    if (jobITP is not null)
-                    {
-                        var jobID = jobITP.Get<JobITP, Guid>(x => x.Job.ID);
-                        dataRow["Job No"] = Jobs.Where(x => x.ID == jobID).FirstOrDefault()?.JobNumber;
-                    }
+                    foreach (var (field, name) in pColumns)
+                        dataRow[field.Replace(".","_")] = $"{row[field]}";
                 }
 
-                if (pColumns != null)
+                if (!string.IsNullOrWhiteSpace(jobLink))
                 {
-                    foreach (var (field, name) in pColumns)
-                    {
-                        dataRow[name] = row[field]?.ToString();
-                    }
+                    dataRow[jobLink.Replace(".","_") + "_ID"] = row[jobLink + ".ID"];
+                    dataRow[jobLink.Replace(".","_") + "_JobNumber"] = row[jobLink + ".JobNumber"]?.ToString();
                 }
 
-                //datarow["Job No"] = (String)row[JobLink + ".JobNumber"];
+                if (!string.IsNullOrWhiteSpace(jobScopeLink))
+                {
+                    dataRow[jobScopeLink.Replace(".","_") + "_ID"] = row[jobScopeLink + ".ID"];
+                    dataRow[jobScopeLink.Replace(".","_") + "_Number"] = row[jobScopeLink + ".Number"]?.ToString();
+                }
 
                 var bHasData = false;
                 if (variables.Any())
@@ -1453,6 +1650,15 @@ public partial class DigitalFormsDashboard : UserControl,
 
     private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
     {
+
+        var jobLink = GetJobLink("", FormType, true) ?? "";
+        if (!string.IsNullOrWhiteSpace(jobLink))
+            jobLink = $"{jobLink.Replace(".","_")}_JobNumber";
+        
+        var jobScopeLink = GetJobScopeLink("", FormType, FormType == typeof(JobForm) ? 0 : 1) ?? "";
+        if (!string.IsNullOrWhiteSpace(jobScopeLink))
+                jobScopeLink = $"{jobScopeLink.Replace(".","_")}_Number";
+        
         e.Column.TextAlignment = TextAlignment.Center;
         e.Column.HorizontalHeaderContentAlignment = HorizontalAlignment.Center;
         e.Column.ColumnSizer = GridLengthUnitType.None;
@@ -1490,17 +1696,25 @@ public partial class DigitalFormsDashboard : UserControl,
                 Path = new PropertyPath(value.Path.Path),
                 Converter = new MileStoneImageConverter()
             };
-            e.Column.MappingName = "Location.Timestamp";
+            e.Column.MappingName = "Location_Timestamp";
         }
-        else if (ParentType is not null && parentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.Any(x => x.Item2.Equals(value.Path.Path)))
+        else if (ParentType is not null && parentColumns.TryGetValue(ParentType, out var pColumns) && pColumns.Any(x => x.Item1.Replace(".","_").Equals(value.Path.Path)))
         {
             e.Column.ColumnSizer = GridLengthUnitType.Auto;
             e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            e.Column.HeaderText = pColumns.FirstOrDefault(x => x.Item1.Replace(".","_").Equals(value.Path.Path))?.Item2 ?? "";
         }
-        else if (value.Path.Path.Equals("Job No"))
+        else if (value.Path.Path.Equals(jobLink))
         {
             e.Column.Width = 60;
             e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            e.Column.HeaderText = "Job No";
+        }
+        else if (value.Path.Path.Equals(jobScopeLink))
+        {
+            e.Column.Width = 50;
+            e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
+            e.Column.HeaderText = "Scope";
         }
         else if (value.Path.Path.Equals("Parent_Description"))
         {
@@ -1543,16 +1757,14 @@ public partial class DigitalFormsDashboard : UserControl,
                 Converter = new DateTimeToStringConverter("dd MMM yy hh:mm")
             };
         }
-        else
+        else if (QuestionCodes.TryGetValue(e.Column.MappingName, out var header))
         {
-            var data = DataGrid.ItemsSource as DataTable;
-            //int index = data.Columns.IndexOf(e.Column.MappingName) - 2;
-            //Style style = new Style(typeof(GridCell));
-            //e.Column.CellStyle = style;
             e.Column.Width = 100;
             e.Column.HeaderStyle = Application.Current.Resources["TemplateHeaderStyle"] as Style;
-            e.Column.HeaderText = QuestionCodes[e.Column.MappingName];
+            e.Column.HeaderText = header;
         }
+        else
+            e.Cancel = true;
     }
 
     private Entity? GetEntityForm<T>(Guid id) where T : Entity, IDigitalFormInstance, IRemotable, IPersistent, new()
@@ -1636,4 +1848,15 @@ public partial class DigitalFormsDashboard : UserControl,
     }
 
     #endregion
+
+    private void DataGrid_SelectionChanged(object? sender, GridSelectionChangedEventArgs e)
+    {
+        var linkcolumn = GetJobLink("", FormType, true);
+        if (!string.IsNullOrWhiteSpace(linkcolumn) && _jobScopeAction != null)
+        {
+            linkcolumn = $"{linkcolumn.Replace(".","_")}_ID";
+            var jobids = DataGrid.SelectedItems.Select(x => (x as DataRowView)!.Row[linkcolumn]).Distinct().ToArray();
+            _jobScopeAction.IsEnabled = jobids.Length == 1;
+        }
+    }
 }

+ 62 - 1
prs.desktop/DockPanels/DigitalForms/DigitalFormDockGrid.cs

@@ -229,7 +229,29 @@ public class DigitalFormDockGrid : DynamicGrid<DigitalFormDockModel>
         else if (typeof(TEntity) == typeof(Job) && typeof(TEntityForm) == typeof(JobForm))
         {
             menu.AddSeparatorIfNeeded();
-            menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, SetJobScopeFromJob_Click);
+            var scopeheader = menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, dockModel => { });
+
+            var scopes = Client.Query<JobScope>(
+                new Filter<JobScope>(x => x.Job.ID).IsEqualTo(formModel.ParentID),
+                Columns.None<JobScope>()
+                    .Add(x => x.ID)
+                    .Add(x => x.Number)
+                    .Add(x => x.Description)
+            ).ToObjects<JobScope>();
+            foreach (var scope in scopes)
+            {
+                var existingscope = new MenuItem() { Header = $"{scope.Number}: {scope.Description}", DataContext = formModel, Tag = scope };
+                existingscope.Click += SetJobScopeFromMenu_Click;
+                scopeheader.Items.Add(existingscope);
+            }
+
+            if (scopes.Any())
+                scopeheader.Items.Add(new Separator());
+            
+            var newscope = new MenuItem() { Header = $"Create New Scope", DataContext = formModel };
+            newscope.Click += CreateJobScopeFromMenu_Click;
+            scopeheader.Items.Add(newscope);
+
         }
 
         if (Security.IsAllowed<CanCustomiseModules>())
@@ -255,6 +277,45 @@ public class DigitalFormDockGrid : DynamicGrid<DigitalFormDockModel>
         }
     }
 
+    
+    private static void UpdateScopeID(DigitalFormDockModel formModel, Guid scopeid)
+    {
+        var instance = Client.Query(
+                new Filter<JobForm>(x => x.ID).IsEqualTo(formModel.ID),
+                Columns.Required<JobForm>()
+            ).ToObjects<JobForm>().First();
+        
+        instance.JobScope.ID = scopeid;
+        Client.Save(instance, "Linked scope set by user from Digital forms dock.");
+
+    }
+    private void CreateJobScopeFromMenu_Click(object sender, RoutedEventArgs e)
+    {
+        if ((sender as MenuItem)?.DataContext is DigitalFormDockModel model)
+        {
+            var scope = new JobScope();
+            scope.Job.ID = model.ParentID;
+            scope.Description = model.FormName;
+            if (new DynamicDataGrid<JobScope>().EditItems(new[] { scope }))
+            {
+                UpdateScopeID(model, scope.ID);
+                MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
+                // Update the form scope here (from the SetScope) stuff
+            }
+        }
+        
+    }
+
+    private void SetJobScopeFromMenu_Click(object sender, RoutedEventArgs e)
+    {
+        if (sender is MenuItem mi && mi.DataContext is DigitalFormDockModel model && mi.Tag is JobScope scope)
+        {
+            UpdateScopeID(model, scope.ID);
+            MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
+            // Update the form scope here (from the SetScope) stuff
+        }
+    }
+
     private static void ViewEntity_Click<TEntity>(DigitalFormDockModel model)
         where TEntity : Entity, IRemotable, IPersistent, new()
     {

+ 73 - 14
prs.desktop/MainWindow.xaml.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.ComponentModel;
 using System.Diagnostics;
 using System.Drawing;
@@ -3202,12 +3203,23 @@ public partial class MainWindow : IPanelHostControl
             ReportsBar.Items.Add(button);
         }
     }
+
+    private NullConverter<Visibility>? _vis = null;
     
     public void CreatePanelAction(PanelAction action)
     {
         if (CurrentTab is null)
             return;
 
+        if (_vis == null)
+        {
+            _vis = new();
+            _vis.Converting += (o, e) =>
+            {
+
+            };
+        }
+        
         var Actions = FindRibbonBar(CurrentTab, x => x.Header.Equals("Actions"));
         if (Actions is not null)
         {
@@ -3218,22 +3230,69 @@ public partial class MainWindow : IPanelHostControl
                 Actions.Items.Add(sep);
             }
 
-            var button = new Fluent.Button
+            if (action.OnPopulate != null)
             {
-                Header = action.Caption,
-                LargeIcon = action.Image?.AsBitmapImage(),
-                MinWidth = 60
-            };
-            button.Bind<PanelAction, String>(Fluent.Button.HeaderProperty, x=>x.Caption, null);
-            button.Bind<PanelAction, Bitmap>(Fluent.Button.LargeIconProperty, x => x.Image, new BitmapToBitmapImageConverter());
-            button.Bind<PanelAction, ContextMenu?>(Fluent.Button.ContextMenuProperty, x=>x.Menu);
-            button.Bind<PanelAction, bool>(Fluent.Button.IsEnabledProperty, x => x.IsEnabled);
-            button.Bind<PanelAction, Visibility>(Fluent.Button.VisibilityProperty, x => x.Visibility);
-            button.DataContext = action;
-            button.Click += (o, e) => action.Execute();
-            Actions.Items.Add(button);
-            CurrentModules.Add(button);
+                Fluent.DropDownButton button = null;
+                if (action.OnExecute != null)
+                {
+                    button = new Fluent.SplitButton();
+                    ((Fluent.SplitButton)button).Click += (o, e) => action.Execute();
+                }
+                else
+                    button = new Fluent.DropDownButton();
+                
+                button.MinWidth = 60;
+                button.DataContext = action;
+                button.DropDownOpened += (sender, args) => { button.ItemsSource = CreateMenuItems(action); };
+                button.Bind<PanelAction, String>(Fluent.DropDownButton.HeaderProperty, x => x.Caption, null);
+                button.Bind<PanelAction, Bitmap?>(Fluent.DropDownButton.LargeIconProperty, x => x.Image,
+                    new BitmapToBitmapImageConverter());
+                button.Bind<PanelAction, ContextMenu?>(ContextMenuProperty, x => x.Menu);
+                button.Bind<PanelAction, bool>(IsEnabledProperty, x => x.IsEnabled);
+                button.Bind<PanelAction, Visibility>(Fluent.DropDownButton.VisibilityProperty, x => x.Visibility, _vis);                    
+                Actions.Items.Add(button);
+                CurrentModules.Add(button);
+            }
+            
+            else
+            {
+                var button = new Fluent.Button()
+                {
+                    MinWidth = 60,
+                    DataContext = action
+                };
+                button.Bind<PanelAction, String>(Fluent.Button.HeaderProperty, x => x.Caption, null);
+                button.Bind<PanelAction, Bitmap?>(Fluent.Button.LargeIconProperty, x => x.Image,
+                    new BitmapToBitmapImageConverter());
+                button.Bind<PanelAction, ContextMenu?>(Fluent.Button.ContextMenuProperty, x => x.Menu);
+                button.Bind<PanelAction, bool>(Fluent.Button.IsEnabledProperty, x => x.IsEnabled);
+                button.Bind<PanelAction, Visibility>(Fluent.Button.VisibilityProperty, x => x.Visibility, _vis);
+                button.Click += (o, e) => action.Execute();
+                Actions.Items.Add(button);
+                CurrentModules.Add(button);
+            }
+        }
+    }
+
+    private static ObservableCollection<MenuItem> CreateMenuItems(PanelAction action)
+    {
+        var items = new ObservableCollection<MenuItem>();
+        var entries = action.Populate();
+        if (entries != null)
+        {
+            foreach (var entry in entries)
+            {
+                var item = new Fluent.MenuItem()
+                {
+                    Header = entry.Caption,
+                    DataContext = entry.Data,
+                    Icon = entry.Image?.AsBitmapImage()
+                };
+                item.Click += (o, eventArgs) => entry.Execute();
+                items.Add(item);
+            }
         }
+        return items;
     }
 
     #endregion

+ 3 - 6
prs.desktop/Panels/Consignments/ConsignmentsPanel.xaml.cs

@@ -65,16 +65,13 @@ namespace PRSDesktop
         {
             if (Security.CanView<ConsignmentType>())
             {
-                host.CreateSetupAction(new PanelAction
-                {
-                    Caption = "Consignment Types",
-                    Image = PRSDesktop.Resources.service,
-                    OnExecute = (action) =>
+                host.CreateSetupAction(new PanelAction("Consignment Types",PRSDesktop.Resources.service,
+                    (action) =>
                     {
                         var list = new MasterList(typeof(ConsignmentType));
                         list.ShowDialog();
                     }
-                });
+                ));
             }
         }
 

+ 13 - 20
prs.desktop/Panels/DataEntry/DataEntryPanel.xaml.cs

@@ -135,30 +135,23 @@ public partial class DataEntryPanel : UserControl, IBasePanel, IDynamicEditorHos
     {
         if (Security.IsAllowed<CanSetupDataEntryTags>())
         {
-            host.CreateSetupAction(new PanelAction
+            host.CreateSetupAction(new PanelAction("Data Entry Tags", null, (action) =>
             {
-                Caption = "Data Entry Tags",
-                OnExecute = (action) =>
-                {
-                    var list = new MasterList(typeof(DataEntryTag));
-                    list.ShowDialog();
-                }
-            });
-            host.CreateSetupAction(new PanelAction
+                var list = new MasterList(typeof(DataEntryTag));
+                list.ShowDialog();
+            }));
+            
+            host.CreateSetupAction(new PanelAction("Settings", null, (action) =>
             {
-                Caption = "Settings",
-                OnExecute = (action) =>
-                {
-                    var settings = new UserConfiguration<DataEntryPanelSettings>().Load();
+                var settings = new UserConfiguration<DataEntryPanelSettings>().Load();
 
-                    var grid = new DynamicItemsListGrid<DataEntryPanelSettings>();
-                    if (grid.EditItems(new DataEntryPanelSettings[] { settings }))
-                    {
-                        new UserConfiguration<DataEntryPanelSettings>().Save(settings);
-                        LoadSettings();
-                    }
+                var grid = new DynamicItemsListGrid<DataEntryPanelSettings>();
+                if (grid.EditItems(new DataEntryPanelSettings[] { settings }))
+                {
+                    new UserConfiguration<DataEntryPanelSettings>().Save(settings);
+                    LoadSettings();
                 }
-            });
+            }));
         }
     }
 

+ 3 - 4
prs.desktop/Panels/PanelHost.cs

@@ -173,12 +173,11 @@ public class PanelHost : IPanelHost
         SetupActions.Clear();
         HostControl.ClearActions();
         HostControl.ClearReports();
-
-        CreateModules(sectionName, model);
+        
         if (CurrentPanel != null)
-        {
             CurrentPanel.CreateToolbarButtons(this);
-        }
+        CreateModules(sectionName, model);
+        
         CreateReports(sectionName, model);
     }
 

+ 65 - 14
prs.desktop/SecondaryWindow.xaml.cs

@@ -22,6 +22,8 @@ using Fluent;
 using Button = Fluent.Button;
 using ContextMenu = System.Windows.Controls.ContextMenu;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using MenuItem = Fluent.MenuItem;
 
 namespace PRSDesktop;
 
@@ -124,23 +126,72 @@ public partial class SecondaryWindow : RibbonWindow, IPanelHostControl
     
     public void CreatePanelAction(PanelAction action)
     {
-        var button = new Fluent.Button
+        
+         if (action.OnPopulate != null)
         {
-            Header = action.Caption,
-            LargeIcon = action.Image?.AsBitmapImage(),
-            MinWidth = 60
-        };
-        button.Bind<PanelAction, String>(Fluent.Button.HeaderProperty, x=>x.Caption, null);
-        button.Bind<PanelAction, Bitmap>(Fluent.Button.LargeIconProperty, x => x.Image, new BitmapToBitmapImageConverter());
-        button.Bind<PanelAction, ContextMenu?>(Fluent.Button.ContextMenuProperty, x=>x.Menu);
-        button.Bind<PanelAction, bool>(Fluent.Button.IsEnabledProperty, x => x.IsEnabled);
-        button.Bind<PanelAction, Visibility>(Fluent.Button.VisibilityProperty, x => x.Visibility);
-        button.DataContext = action;
-        button.Click += (o, e) => action.Execute();
-        Actions.Items.Add(button);
-        CurrentModules.Add(button);
+            Fluent.DropDownButton button = null;
+            if (action.OnExecute != null)
+            {
+                button = new Fluent.SplitButton();
+                ((Fluent.SplitButton)button).Click += (o, e) => action.Execute();
+            }
+            else
+                button = new Fluent.DropDownButton();
+            
+            button.MinWidth = 60;
+            button.DataContext = action;
+            button.DropDownOpened += (sender, args) => { button.ItemsSource = CreateMenuItems(action); };
+            button.Bind<PanelAction, String>(Fluent.DropDownButton.HeaderProperty, x => x.Caption, null);
+            button.Bind<PanelAction, Bitmap?>(Fluent.DropDownButton.LargeIconProperty, x => x.Image,
+                new BitmapToBitmapImageConverter());
+            button.Bind<PanelAction, ContextMenu?>(ContextMenuProperty, x => x.Menu);
+            button.Bind<PanelAction, bool>(IsEnabledProperty, x => x.IsEnabled);
+            button.Bind<PanelAction, Visibility>(VisibilityProperty, x => x.Visibility);                    
+            Actions.Items.Add(button);
+            CurrentModules.Add(button);
+        }
+        
+        else
+        {
+            var button = new Fluent.Button()
+            {
+                MinWidth = 60,
+                DataContext = action
+            };
+            button.Bind<PanelAction, String>(Fluent.Button.HeaderProperty, x => x.Caption, null);
+            button.Bind<PanelAction, Bitmap?>(Fluent.Button.LargeIconProperty, x => x.Image,
+                new BitmapToBitmapImageConverter());
+            button.Bind<PanelAction, ContextMenu?>(Fluent.Button.ContextMenuProperty, x => x.Menu);
+            button.Bind<PanelAction, bool>(Fluent.Button.IsEnabledProperty, x => x.IsEnabled);
+            button.Bind<PanelAction, Visibility>(Fluent.Button.VisibilityProperty, x => x.Visibility);
+            button.Click += (o, e) => action.Execute();
+            Actions.Items.Add(button);
+            CurrentModules.Add(button);
+        }
+         
         ActionsSeparator.Visibility = Visibility.Visible;
     }
+    
+    private static ObservableCollection<MenuItem> CreateMenuItems(PanelAction action)
+    {
+        var items = new ObservableCollection<MenuItem>();
+        var entries = action.Populate();
+        if (entries != null)
+        {
+            foreach (var entry in entries)
+            {
+                var item = new Fluent.MenuItem()
+                {
+                    Header = entry.Caption,
+                    DataContext = entry.Data,
+                    Icon = entry.Image?.AsBitmapImage()
+                };
+                item.Click += (o, eventArgs) => entry.Execute();
+                items.Add(item);
+            }
+        }
+        return items;
+    }
 
     public void CreateReport(PanelAction action)
     {