Преглед на файлове

Merge remote-tracking branch 'origin/kenric' into frank

frogsoftware преди 1 година
родител
ревизия
69f5c25a6a

+ 2 - 0
prs.classes/Entities/Job/JobForm.cs

@@ -4,6 +4,8 @@ namespace Comal.Classes
 {
     public class JobForm : EntityForm<Job, JobLink, JobForm>, IDigitalForm<Job>, ILicense<ProjectManagementLicense>
     {
+        public JobScopeLink JobScope { get; set; }
+
         public override string AutoIncrementPrefix() => "JF";
     }
 }

+ 18 - 3
prs.classes/Entities/Job/JobScopes/JobScopeLookups.cs

@@ -4,7 +4,7 @@ using InABox.Core;
 namespace Comal.Classes
 {
     public class JobScopeLookups : EntityLookup<JobScope>, ILookupDefinition<JobScope, InvoiceLine>, ILookupDefinition<JobScope, Job>, ILookupDefinition<JobScope, StagingSetout>
-        , ILookupDefinition<JobScope, Assignment>, ILookupDefinition<JobScope, RequisitionItem>, ILookupDefinition<JobScope, Requisition>
+        , ILookupDefinition<JobScope, Assignment>, ILookupDefinition<JobScope, Kanban>, ILookupDefinition<JobScope, RequisitionItem>, ILookupDefinition<JobScope, Requisition>
     {
         
         #region Invoice Lines
@@ -104,9 +104,24 @@ namespace Comal.Classes
         
         Columns<Requisition> ILookupDefinition<JobScope, Requisition>.DefineFilterColumns()
             => new Columns<Requisition>(x=>x.JobLink.ID);
-        
+
         #endregion
-        
+
+        #region
+
+        public Filter<JobScope> DefineFilter(Kanban[] items)
+        {
+            var item = items?.Length == 1 ? items[0] : null;
+            if (item != null)
+                return new Filter<JobScope>(x => x.Job.ID).IsEqualTo(item.JobLink.ID).And(x => x.Status.Approved).IsEqualTo(true);
+            return new Filter<JobScope>(x => x.ID).None();
+        }
+
+        Columns<Kanban> ILookupDefinition<JobScope, Kanban>.DefineFilterColumns()
+            => new Columns<Kanban>(x => x.JobLink.ID);
+
+        #endregion
+
         public override Columns<JobScope> DefineColumns()
         {
             return new Columns<JobScope>().Default();

+ 17 - 10
prs.classes/Entities/Kanban/Kanban.cs

@@ -42,7 +42,7 @@ namespace Comal.Classes
     [UserTracking("Task Management")]
     [Caption("Tasks")]
     public class Kanban : Entity, IPersistent, IRemotable, IKanban, IScheduleAction, IOneToMany<Schedule>, INumericAutoIncrement<Kanban>,
-        IOneToMany<Equipment>, ILicense<TaskManagementLicense>, IExportable, IImportable
+        IOneToMany<Equipment>, ILicense<TaskManagementLicense>, IExportable, IImportable, IJobScopedItem
     {
         
         public static String OPEN => "Open";
@@ -66,39 +66,41 @@ namespace Comal.Classes
         public string Title { get; set; }
 
         [EditorSequence(1)]
-        [Caption("Attached to")]
         public JobLink JobLink { get; set; }
 
-        [RichTextEditor]
         [EditorSequence(2)]
+        public JobScopeLink JobScope { get; set; }
+
+        [RichTextEditor]
+        [EditorSequence(3)]
         public string Description { get; set; }
 
         [NotesEditor]
-        [EditorSequence(3)]
+        [EditorSequence(4)]
         public string[] Notes { get; set; } = Array.Empty<string>();
 
         [MemoEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
-        [EditorSequence(4)]
+        [EditorSequence(5)]
         public string Summary { get; set; }
 
-        [EditorSequence(5)]
+        [EditorSequence(6)]
         [Caption("Task Type")]
         public KanbanTypeLink Type { get; set; }
 
         [DateEditor]
-        [EditorSequence(6)]
+        [EditorSequence(7)]
         public DateTime DueDate { get; set; } = DateTime.Today;
 
-        [EditorSequence(7)]
+        [EditorSequence(8)]
         [Caption("Assigned To")]
         public EmployeeLink EmployeeLink { get; set; }
 
-        [EditorSequence(8)]
+        [EditorSequence(9)]
         [Caption("Allocated By")]
         public EmployeeLink ManagerLink { get; set; }
 
         [DateTimeEditor(Editable = Editable.Hidden)]
-        [EditorSequence(9)]
+        [EditorSequence(10)]
         public DateTime Completed { get; set; } = DateTime.MinValue;
 
         [EditorSequence("Other", 1)]
@@ -206,6 +208,11 @@ namespace Comal.Classes
             bChanging = false;
         }
 
+        static Kanban()
+        {
+            Classes.JobScope.LinkScopeProperties<Kanban>();
+        }
+
         private class KanbanCategoryLookups : LookupGenerator<object>
         {
             public KanbanCategoryLookups(object[] items) : base(items)

+ 1 - 0
prs.classes/Entities/Manufacturing/ManufacturingPacket/ManufacturingPacketStage.cs

@@ -116,6 +116,7 @@ namespace Comal.Classes
         {
             return Parent.ID;
         }
+        public Type ParentType() => typeof(ManufacturingPacket);
 
         [NullEditor]
         [Obsolete("Being Replaced by FormData")]

+ 275 - 6
prs.desktop/DockPanels/DigitalFormsDock.xaml.cs

@@ -16,6 +16,8 @@ using InABox.Scripting;
 using InABox.Wpf.Reports;
 using InABox.WPF;
 using PRSDesktop.Configuration;
+using System.Diagnostics;
+using System.Threading.Tasks;
 
 namespace PRSDesktop
 {
@@ -50,6 +52,8 @@ namespace PRSDesktop
         [NullEditor]
         public DateTime Processed { get; set; }
 
+        [NullEditor]
+        public Guid ParentID { get; set; }
     }
     
     public class DigitalFormDockGrid : DynamicGrid<DigitalFormDockModel>
@@ -98,7 +102,8 @@ namespace PRSDesktop
                 .Add(x => x.Form.Description, x => x.FormName)
                 .Add(x => x.FormCompleted, x => x.Completed)
                 .Add(x => x.FormCompletedBy.UserID, x => x.CompletedBy)
-                .Add(x => x.FormProcessed, x => x.Processed);
+                .Add(x => x.FormProcessed, x => x.Processed)
+                .Add(x => x.Parent.ID, x => x.ParentID);
             
             _query = new MultiQuery();
             
@@ -194,7 +199,6 @@ namespace PRSDesktop
                 );
             }
 
-            
             if (modules.Any())
                 column.AddSeparator();
             
@@ -205,8 +209,8 @@ namespace PRSDesktop
                     PRSDesktop.Resources.lock_sml,
                     (row) =>
                     {
-                        var form = Activator.CreateInstance(formType) as IDigitalFormInstance;
-                        form.ID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
+                        var form = (Activator.CreateInstance(formType) as IDigitalFormInstance)!;
+                        form.ID = row!.Get<DigitalFormDockModel, Guid>(x => x.ID);
                         form.FormProcessed = row.Get<DigitalFormDockModel, DateTime>(x => x.Processed);
                         form.CommitChanges();
                         form.FormProcessed = DateTime.Now;
@@ -225,8 +229,8 @@ namespace PRSDesktop
                     PRSDesktop.Resources.lock_sml,
                     (row) =>
                     {
-                        var form = Activator.CreateInstance(formType) as IDigitalFormInstance;
-                        form.ID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
+                        var form = (Activator.CreateInstance(formType) as IDigitalFormInstance)!;
+                        form.ID = row!.Get<DigitalFormDockModel, Guid>(x => x.ID);
                         form.FormProcessed = row.Get<DigitalFormDockModel, DateTime>(x => x.Processed);
                         form.CommitChanges();
                         form.FormProcessed = DateTime.MinValue;
@@ -239,6 +243,111 @@ namespace PRSDesktop
                 );
             }
 
+            var entityType = DFUtils.FormEntityType(formType);
+            if(entityType.HasInterface<IJobScopedItem>())
+            {
+                var entityID = row.Get<DigitalFormDockModel, Guid>(x => x.ParentID);
+                column.AddSeparator();
+                column.AddItem("Set Job", PRSDesktop.Resources.project, (row) =>
+                {
+                    var entity = (Client.Create(entityType)
+                        .Query(
+                            Filter.Create<Entity>(entityType, x => x.ID).IsEqualTo(entityID),
+                            LookupFactory.DefineFilterColumns(typeof(Job), entityType)
+                                .Add<Entity>(x => x.ID)
+                                .Add<IJobScopedItem>(x => x.JobLink.ID)
+                                .Add<IJobScopedItem>(x => x.JobLink.JobNumber))
+                        .ToObjects(entityType).First() as Entity)!;
+                    var item = (entity as IJobScopedItem)!;
+
+                    var arr = Array.CreateInstance(entityType, 1);
+                    arr.SetValue(entity, 0);
+                    var window = new MultiSelectDialog<Job>(
+                        LookupFactory.DefineFilter<Job>(entityType, arr),
+                        new Columns<Job>(x => x.DefaultScope.ID).Add(x => x.JobNumber),
+                        multiselect: false);
+                    if (!window.ShowDialog(nameof(Job.JobNumber), item.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals))
+                    {
+                        return;
+                    }
+                    var job = window.Data().ToObjects<Job>().First();
+                    item.JobLink.ID = job.ID;
+                    item.JobLink.Synchronise(job);
+
+                    Client.Create(entityType).Save(entity, "Linked job set by user from Digital forms dock.");
+                    MessageBox.Show($"{entityType.Name} has been assigned to job {job.JobNumber}.");
+                });
+                column.AddItem("Set Job Scope", PRSDesktop.Resources.project, (row) =>
+                {
+                    var entity = (Client.Create(entityType)
+                        .Query(
+                            Filter.Create<Entity>(entityType, x => x.ID).IsEqualTo(entityID),
+                            LookupFactory.DefineFilterColumns(typeof(JobScope), entityType)
+                                .Add<Entity>(x => x.ID)
+                                .Add<IJobScopedItem>(x => x.JobLink.ID)
+                                .Add<IJobScopedItem>(x => x.JobScope.ID)
+                                .Add<IJobScopedItem>(x => x.JobScope.Number))
+                        .ToObjects(entityType).First() as Entity)!;
+                    var item = (entity as IJobScopedItem)!;
+                    if(item.JobLink.ID == Guid.Empty)
+                    {
+                        MessageBox.Show($"{entityType.Name} is not linked to a job. Please select a job first.");
+                        return;
+                    }
+
+                    var arr = Array.CreateInstance(entityType, 1);
+                    arr.SetValue(entity, 0);
+                    var window = new MultiSelectDialog<JobScope>(
+                        new Filters<JobScope>()
+                            .Add(LookupFactory.DefineFilter<JobScope>(entityType, arr))
+                            .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(item.JobLink.ID))
+                            .Combine(),
+                        new Columns<JobScope>(x => x.ID).Add(x => x.Number),
+                        multiselect: false);
+                    if (!window.ShowDialog(nameof(JobScope.Number), item.JobScope.Number, Syncfusion.Data.FilterType.Equals))
+                    {
+                        return;
+                    }
+                    var scope = window.Data().ToObjects<JobScope>().First();
+                    item.JobScope.ID = scope.ID;
+
+                    Client.Create(entityType).Save(entity, "Linked scope set by user from Digital forms dock.");
+                    MessageBox.Show($"{entityType.Name} has been assigned to scope {scope.Number}.");
+                });
+            }
+            else if(entityType == typeof(Job) && formType == typeof(JobForm))
+            {
+                column.AddSeparator();
+                column.AddItem("Set Job Scope", PRSDesktop.Resources.project, (row) =>
+                {
+                    var instance = Client.Query(
+                        new Filter<JobForm>(x => x.ID).IsEqualTo(instanceID),
+                        LookupFactory.DefineFilterColumns<JobForm, JobScope>()
+                            .Add(x => x.ID)
+                            .Add(x => x.Parent.ID))
+                        .ToObjects<JobForm>().First();
+                    var job = Client.Query(
+                        new Filter<Job>(x => x.ID).IsEqualTo(instance.Parent.ID),
+                        LookupFactory.DefineFilterColumns<Job, JobScope>()).ToObjects<Job>().First();
+
+                    var window = new MultiSelectDialog<JobScope>(
+                        new Filters<JobScope>()
+                            .Add(LookupFactory.DefineFilter<Job, JobScope>(new Job[] { job }))
+                            .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(instance.Parent.ID))
+                            .Combine(),
+                        new Columns<JobScope>(x => x.ID).Add(x => x.Number), multiselect: false);
+                    if (!window.ShowDialog())
+                    {
+                        return;
+                    }
+                    var scope = window.Data().ToObjects<JobScope>().First();
+                    instance.JobScope.ID = scope.ID;
+
+                    Client.Save(instance, "Linked scope set by user from Digital forms dock.");
+                    MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
+                });
+            }
+
 
             if (Security.IsAllowed<CanCustomiseModules>())
             {
@@ -263,6 +372,165 @@ namespace PRSDesktop
             }
         }
 
+        /*private void AttachToScope(CoreRow row, Type formType, DataModel model)
+        {
+            var instance = Client.Create(formType)
+                .Query(
+                    Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(row.Get<DigitalFormDockModel, Guid>(x => x.ID)),
+                    Columns.Create<IDigitalFormInstance>(formType)
+                        .Add<IDigitalFormInstance>(x => x.ID)
+                        .Add<IDigitalFormInstance>(x => x.Number))
+                .Rows.FirstOrDefault()
+                ?.ToObject(formType) as IDigitalFormInstance;
+            if (instance is null)
+            {
+                MessageBox.Show("Form does not exist!");
+                return;
+            }
+            var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
+
+            var entityColumns = JobScopeForms.GetEntityColumns(formType)?.Add<Entity>(x => x.ID);
+            var entityObj = Client.Create(instance.ParentType())
+                .Query(
+                    Filter.Create<Entity>(instance.ParentType(), x => x.ID).IsEqualTo(row.Get<DigitalFormDockModel, Guid>(x => x.ParentID)),
+                    entityColumns).ToObjects(instance.ParentType()).FirstOrDefault();
+            if (entityObj is not Entity entity)
+            {
+                MessageBox.Show($"Attached {instance.ParentType().Name} does not exist!");
+                return;
+            }
+
+            #region Selecting Scope
+
+            // JobScopes obviously can't have an empty ID, so it's fine that I turn null into Guid.Empty here.
+            var scopeID = JobScopeForms.GetJobScopeID(formType, instance, entity) ?? Guid.Empty;
+            if(scopeID == Guid.Empty)
+            {
+                // JobScopes can't have an empty job, so it's fine that I turn null into Guid.Empty here.
+                var jobID = JobScopeForms.GetJobID(formType, instance, entity) ?? Guid.Empty;
+                if(jobID == Guid.Empty)
+                {
+                    var jobs = new MultiSelectDialog<Job>(null, null, multiselect: false);
+                    if(!jobs.ShowDialog("Select a job"))
+                    {
+                        MessageBox.Show("No job selected; process cancelled.");
+                        return;
+                    }
+                    jobID = jobs.IDs().First();
+                }
+
+                var scopes = new MultiSelectDialog<JobScope>(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(jobID), null, multiselect: false);
+                if (!scopes.ShowDialog("Select a scope"))
+                {
+                    MessageBox.Show("Process cancelled.");
+                    return;
+                }
+                scopeID = scopes.IDs().First();
+            }
+
+            var scopeTask = Task.Run(() => Client.QueryMultiple(
+                new KeyedQueryDef<JobScope>(
+                    new Filter<JobScope>(x => x.ID).IsEqualTo(scopeID),
+                    new Columns<JobScope>(x => x.ID).Add(x => x.Number)),
+                new KeyedQueryDef<Job>(
+                    new Filter<Job>(x => x.ID).InQuery(new Filter<JobScope>(x => x.ID).IsEqualTo(scopeID), x => x.Job.ID),
+                    new Columns<Job>(x => x.JobNumber))));
+
+            #endregion
+
+            #region Selecting Report
+
+            var reports = new List<ReportTemplate>();
+            Progress.ShowModal("Loading Document", (progress) =>
+            {
+                reports = ReportUtils.LoadReports(formID.ToString(), model).ToList();
+            });
+
+            ReportTemplate report;
+            if (reports.Count == 0)
+            {
+                ReportTemplate reportTemplate = null!;
+                Progress.ShowModal("Loading form layout", (progress) =>
+                {
+                    progress.Report("Loading form layout");
+                    var layouts = Client.Query(
+                        new Filter<DigitalFormLayout>(x => x.Form.ID).IsEqualTo(formID),
+                        new Columns<DigitalFormLayout>(x => x.Layout)
+                            .Add(x => x.Code)
+                            .Add(x => x.Type)
+                            .Add(x => x.Description))
+                        .ToObjects<DigitalFormLayout>().ToArray();
+                    var layout = layouts.FirstOrDefault(x => x.Type == DFLayoutType.Desktop) ?? layouts.FirstOrDefault();
+                    if (layout is null)
+                    {
+                        MessageBox.Show("This form has no report or layout, so it cannot be attached to a job scope!");
+                        return;
+                    }
+
+                    progress.Report("Generating report");
+
+                    report = new ReportTemplate
+                    {
+                        DataModel = model.Name,
+                        Name = layout.Description.NotWhiteSpaceOr(layout.Code),
+                        RDL = DigitalFormUtils.GenerateReport(layout, model)?.SaveToString()
+                    };
+                });
+                report = reportTemplate;
+            }
+            else if (reports.Count == 1)
+            {
+                report = reports[0];
+            }
+            else
+            {
+                var selectedReports = new MultiSelectDialog<ReportTemplate>(
+                    ReportUtils.GetReportFilter(formID.ToString(), model),
+                    null,
+                    multiselect: false);
+                if (!selectedReports.ShowDialog())
+                {
+                    return;
+                }
+                report = selectedReports.Items().First();
+            }
+
+            #endregion
+
+            Document document = null!;
+
+            Progress.ShowModal("Loading Document", (progress) =>
+            {
+                progress.Report("Generating Document");
+
+                var data = ReportUtils.ReportToPDF(report, model, true);
+
+                progress.Report("Saving Document");
+
+                document = new Document
+                {
+                    FileName = $"{instance.Number}: {report.Name}.pdf",
+                    Data = data,
+                    CRC = CoreUtils.CalculateCRC(data)
+                };
+                Client.Save(document, "");
+
+                progress.Report("Creating Job Scope Document");
+
+                var jobScopeDocument = new JobScopeDocument();
+                jobScopeDocument.DocumentLink.ID = document.ID;
+                jobScopeDocument.EntityLink.ID = scopeID;
+                Client.Save(jobScopeDocument, $"Generated from form: {instance.Number}");
+            });
+
+            scopeTask.Wait();
+            var scopeResults = scopeTask.Result;
+            var scope = scopeResults.GetObjects<JobScope>().First();
+            var job = scopeResults.GetObjects<Job>().First();
+
+            MessageBox.Show($"Attached as '{document.FileName}' to job scope {scope.Number} for job {job.JobNumber}");
+        }*/
+
         private DynamicMenuStatus MenuStatus(CoreRow row)
         {
             if (row == null) return DynamicMenuStatus.Hidden;
@@ -281,6 +549,7 @@ namespace PRSDesktop
 
                 var cols = Columns.Create<IDigitalFormInstance>(type)
                     .Add<IDigitalFormInstance>(c => c.ID)
+                    .Add<IDigitalFormInstance>(c => c.Parent.ID)
                     .Add<IDigitalFormInstance>(c => c.Form.ID)
                     .Add<IDigitalFormInstance>(c => c.Number)
                     .Add<IDigitalFormInstance>(c => c.Form.Description)

+ 2 - 2
prs.desktop/MainWindow.xaml.cs

@@ -1270,7 +1270,7 @@ namespace PRSDesktop
                 true);
             //AddSetupAction(ProjectsTab, "Document Tags", JobDocumentTagButton_OnClick, PRSDesktop.Resources.checklist,
             //    true);
-            AddSetupAction(ProjectsTab, "Financial Statuses", FinancialStatusButton_Click, PRSDesktop.Resources.view,
+            AddSetupAction(ProjectsTab, "Job Scope Statuses", JobScopeStatusButton_Click, PRSDesktop.Resources.view,
                 Security.CanView<JobScope>());
             AddSetupAction(ProjectsTab, "Drawing Templates", DrawingTemplatesButton_Click, PRSDesktop.Resources.doc_misc,
                 true);
@@ -2304,7 +2304,7 @@ namespace PRSDesktop
             list.ShowDialog();
         }
 
-        private void FinancialStatusButton_Click()
+        private void JobScopeStatusButton_Click()
         {
             var list = new MasterList(typeof(JobScopeStatus));
             list.ShowDialog();

+ 30 - 0
prs.desktop/Panels/Jobs/JobScopes/JobScopeFormGrid.cs

@@ -0,0 +1,30 @@
+using System;
+using Comal.Classes;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace PRSDesktop;
+
+public class JobScopeFormGrid : DynamicEntityFormGrid<JobForm, Job, JobLink>, IJobScopeGrid
+{
+    public JobScope? Scope { get; set; }
+
+    public JobScopeFormGrid(Job job): base(job)
+    {
+    }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        options.Remove(DynamicGridOption.AddRows);
+    }
+
+    protected override void Reload(Filters<JobForm> criteria, Columns<JobForm> columns, ref SortOrder<JobForm>? sort, Action<CoreTable?, Exception?> action)
+    {
+        if ((Scope == null) || (Scope.ID == Guid.Empty))
+            criteria.Add(new Filter<JobForm>(x => x.ID).None());
+        else
+            criteria.Add(new Filter<JobForm>(x => x.JobScope.ID).IsEqualTo(Scope.ID));
+        base.Reload(criteria, columns, ref sort, action);
+    }
+}

+ 26 - 0
prs.desktop/Panels/Jobs/JobScopes/JobScopeKanbanGrid.cs

@@ -0,0 +1,26 @@
+using System;
+using Comal.Classes;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace PRSDesktop;
+
+public class JobScopeKanbanGrid : DynamicDataGrid<Kanban>, IJobScopeGrid
+{
+    public JobScope? Scope { get; set; }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        options.Remove(DynamicGridOption.AddRows);
+    }
+
+    protected override void Reload(Filters<Kanban> criteria, Columns<Kanban> columns, ref SortOrder<Kanban>? sort, Action<CoreTable?, Exception?> action)
+    {
+        if ((Scope == null) || (Scope.ID == Guid.Empty))
+            criteria.Add(new Filter<Kanban>(x => x.ID).None());
+        else
+            criteria.Add(new Filter<Kanban>(x => x.JobScope.ID).IsEqualTo(Scope.ID));
+        base.Reload(criteria, columns, ref sort, action);
+    }
+}

+ 2 - 0
prs.desktop/Panels/Jobs/JobScopes/JobScopePanel.xaml

@@ -38,6 +38,8 @@
                     <dynamicGrid:DynamicTabItem Header="Delivery Items" x:Name="_deliveryItems" />
                     <dynamicGrid:DynamicTabItem Header="Activities" x:Name="_assignments" />
                     <dynamicGrid:DynamicTabItem Header="Documents" x:Name="_documents" />
+                    <dynamicGrid:DynamicTabItem Header="Tasks" x:Name="_kanbans" />
+                    <dynamicGrid:DynamicTabItem Header="Forms" x:Name="_forms" />
                 </dynamicGrid:DynamicTabControl.Items>
             </dynamicGrid:DynamicTabControl>
         </dynamicGrid:DynamicSplitPanel.Detail>

+ 18 - 4
prs.desktop/Panels/Jobs/JobScopes/JobScopePanel.xaml.cs

@@ -19,7 +19,9 @@ public partial class JobScopePanel : UserControl, IPanel<JobScope>, IJobControl
         ManufacturingPackets,
         DeliveryItems,
         Assignments,
-        Documents
+        Documents,
+        Tasks,
+        Forms,
     }
             
     private int _currentPage = -1;
@@ -27,6 +29,8 @@ public partial class JobScopePanel : UserControl, IPanel<JobScope>, IJobControl
     private JobScopeRequisitionItemGrid? _requisitionItemsGrid;
     private JobScopeAssignmentGrid? _assignmentGrid;
     private JobScopeDocumentGrid? _documentGrid;
+    private JobScopeKanbanGrid? _kanbanGrid;
+    private JobScopeFormGrid? _formGrid;
 
     public JobScopePanel()
     {
@@ -89,6 +93,12 @@ public partial class JobScopePanel : UserControl, IPanel<JobScope>, IJobControl
             case PageIndex.Documents:
                 RefreshGrid(_documents, ref _documentGrid, scope);
                 break;
+            case PageIndex.Tasks:
+                RefreshGrid(_kanbans, ref _kanbanGrid, scope);
+                break;
+            case PageIndex.Forms:
+                RefreshGrid(_forms, ref _formGrid, scope, () => new JobScopeFormGrid(Job));
+                break;
         }
     }
 
@@ -113,13 +123,17 @@ public partial class JobScopePanel : UserControl, IPanel<JobScope>, IJobControl
     {
         RefreshPage();
     }
-    
-    private void RefreshGrid<T>(TabItem container, ref T? grid, JobScope? scope) where T : IDynamicGrid, IJobScopeGrid, new()
+
+    private void RefreshGrid<T>(TabItem container, ref T? grid, JobScope? scope) where T : IDynamicGrid, IJobScopeGrid, new() =>
+        RefreshGrid(container, ref grid, scope, () => new T());
+
+    private void RefreshGrid<T>(TabItem container, ref T? grid, JobScope? scope, Func<T> newGrid)
+        where T : IDynamicGrid, IJobScopeGrid
     {
         var bInitialised = grid != null;
         if (!bInitialised)
         {
-            grid = new T();
+            grid = newGrid();
             container.Content = grid;
         }
         grid.Scope = scope;