Explorar el Código

Added drag drop support for Job document set milestones, and added support for printing forms to milestones.

Kenric Nugteren hace 1 año
padre
commit
b20e2bc2f9

+ 1 - 0
prs.classes/Entities/Job/Requisitions/JobRequisitionItemLink.cs

@@ -36,6 +36,7 @@ namespace Comal.Classes
         public JobRequisitionItemStatus Status { get; set; } = JobRequisitionItemStatus.NotChecked;
 
         [IntegerEditor(Visible = Visible.Default, Editable = Editable.Disabled)]
+        [Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")]
         public PurchaseOrderItemLink PurchaseOrderItem { get; set; }
     }
 }

+ 1 - 1
prs.desktop/Panels/DataEntry/DataEntryReGroupWindow.xaml.cs

@@ -313,7 +313,7 @@ namespace PRSDesktop
             throw new Exception("Could not render file to PDF");
         }
 
-        private static PdfDocumentBase RenderToPDF(string filename)
+        public static PdfDocumentBase RenderToPDF(string filename)
         {
             using var stream = new FileStream(filename, FileMode.Open);
             return RenderToPDF(filename, stream);

+ 4 - 3
prs.desktop/Panels/Invoices/InvoiceGrid.cs

@@ -188,7 +188,7 @@ namespace PRSDesktop
             return false;
         }
 
-        private void PrintInvoice(object sender, object item)
+        private void PrintInvoice(object sender, object? item)
         {
             var inv = (Invoice)sender;
             DoPrintInvoice(inv.JobLink.ID, inv.ID, inv.CustomerLink.ID);
@@ -221,9 +221,10 @@ namespace PRSDesktop
             return false;
         }
 
-        private void EmailInvoice(object sender, object item)
+        private void EmailInvoice(object sender, object? item)
         {
-            var inv = (Invoice)item;
+            if (item is not Invoice inv) return;
+
             DoEmailInvoice(inv.ID, inv.Number, inv.CustomerLink.ID);
         }
 

+ 11 - 12
prs.desktop/Panels/Jobs/DocumentSets/JobDocumentSetMileStoneBlock.cs

@@ -1,17 +1,16 @@
 using System;
 using Comal.Classes;
 
-namespace PRSDesktop
+namespace PRSDesktop;
+
+public class JobDocumentSetMileStoneBlock
 {
-    public class JobDocumentSetMileStoneBlock
-    {
-        public Guid ID { get; set; }
-        public String Revision { get; set; }
-        public JobDocumentSetMileStoneStatus Status { get; set; }
-        public DateTime Date { get; set; }
-        public String Notes { get; set; }
-        public int Attachments { get; set; }
-        public int Kanbans { get; set; }
-        public String Watermark { get; set; }
-    }
+    public Guid ID { get; set; }
+    public String Revision { get; set; }
+    public JobDocumentSetMileStoneStatus Status { get; set; }
+    public DateTime Date { get; set; }
+    public String Notes { get; set; }
+    public int Attachments { get; set; }
+    public int Kanbans { get; set; }
+    public String Watermark { get; set; }
 }

+ 35 - 32
prs.desktop/Panels/Jobs/DocumentSets/JobDocumentSetTree.xaml

@@ -168,7 +168,7 @@
         <!-- </Style>  -->
         
     </UserControl.Resources>
-    <Grid>
+    <Grid x:Name="ContainerGrid">
         <Grid.RowDefinitions>
             <RowDefinition Height="*"/>
             <RowDefinition Height="Auto"/>
@@ -176,39 +176,42 @@
 
         <!-- SelectionUnit="Cell" -->
         <!-- QueryRowHeight="DataGrid_OnQueryRowHeight" -->
-        
-        <Syncfusion:SfTreeGrid
-            x:Name="treeGrid"
-            Grid.Row="0"
-            AutoGenerateColumns="False"
-            ChildPropertyName="Children"
-            AutoExpandMode="AllNodesExpanded"
-            NodeCollapsing="TreeGrid_OnNodeCollapsing"
-            RowHeight="60"
-            AllowSorting="False"
-            HeaderRowHeight="30"
-            NavigationMode="Cell"
-            SelectionMode="Extended"
-            ColumnSizer="Auto"
-            Background="DimGray"
-            HeaderStyle="{StaticResource headerStyle}"
-            RowStyle="{StaticResource rowStyle}"
-            CellToolTipOpening="TreeGrid_OnCellToolTipOpening"
-            ContextMenuOpening="TreeGrid_OnContextMenuOpening"
-            ItemsSourceChanged="TreeGrid_OnItemsSourceChanged"
-            SelectionChanged="TreeGrid_OnSelectionChanged"
-            CurrentCellActivated="TreeGrid_OnCurrentCellActivated"
-            CellDoubleTapped="TreeGrid_OnCellDoubleTapped"
-        >
-            <Syncfusion:SfTreeGrid.StackedHeaderRows>
-                <Syncfusion:StackedHeaderRow x:Name="stackedHeaderRow" />
-            </Syncfusion:SfTreeGrid.StackedHeaderRows>
 
-            <Syncfusion:SfTreeGrid.ContextMenu>
-                <ContextMenu x:Name="MileStoneMenu" />
-            </Syncfusion:SfTreeGrid.ContextMenu>
+        <Border x:Name="TreeBorder" AllowDrop="True"
+                Drop="Grid_Drop">
+            <Syncfusion:SfTreeGrid x:Name="treeGrid"
+                                Grid.Row="0"
+                                AutoGenerateColumns="False"
+                                ChildPropertyName="Children"
+                                AutoExpandMode="AllNodesExpanded"
+                                NodeCollapsing="TreeGrid_OnNodeCollapsing"
+                                RowHeight="60"
+                                AllowSorting="False"
+                                HeaderRowHeight="30"
+                                NavigationMode="Cell"
+                                SelectionMode="Extended"
+                                ColumnSizer="Auto"
+                                Background="DimGray"
+                                HeaderStyle="{StaticResource headerStyle}"
+                                RowStyle="{StaticResource rowStyle}"
+                                CellToolTipOpening="TreeGrid_OnCellToolTipOpening"
+                                ContextMenuOpening="TreeGrid_OnContextMenuOpening"
+                                ItemsSourceChanged="TreeGrid_OnItemsSourceChanged"
+                                SelectionChanged="TreeGrid_OnSelectionChanged"
+                                CurrentCellActivated="TreeGrid_OnCurrentCellActivated"
+                                CellDoubleTapped="TreeGrid_OnCellDoubleTapped"
+                                AllowDraggingRows="False"
+                                AllowDrop="False">
+                <Syncfusion:SfTreeGrid.StackedHeaderRows>
+                    <Syncfusion:StackedHeaderRow x:Name="stackedHeaderRow" />
+                </Syncfusion:SfTreeGrid.StackedHeaderRows>
+
+                <Syncfusion:SfTreeGrid.ContextMenu>
+                    <ContextMenu x:Name="MileStoneMenu" />
+                </Syncfusion:SfTreeGrid.ContextMenu>
 
-        </Syncfusion:SfTreeGrid>
+            </Syncfusion:SfTreeGrid>
+        </Border>
         
         <DockPanel Grid.Row="1">
             <Button x:Name="Add" DockPanel.Dock="Left" Margin="0,2,2,0" Width="30" Height="30"

+ 433 - 286
prs.desktop/Panels/Jobs/DocumentSets/JobDocumentSetTree.xaml.cs

@@ -10,17 +10,24 @@ using System.Windows.Forms;
 using System.Windows.Input;
 using System.Windows.Media;
 using Comal.Classes;
+using FastReport.Utils;
 using InABox.Clients;
 using InABox.Configuration;
 using InABox.Core;
+using InABox.Core.Reports;
 using InABox.DynamicGrid;
+using InABox.Wpf;
+using InABox.Wpf.Reports;
 using InABox.WPF;
-using Microsoft.Exchange.WebServices.Data;
+using javax.xml.crypto;
+using NPOI.SS.Formula.Functions;
+using org.apache.commons.lang3;
 using Syncfusion.Compression.Zip;
 using Syncfusion.Data.Extensions;
 using Syncfusion.UI.Xaml.Grid;
 using Syncfusion.UI.Xaml.TreeGrid;
 using Syncfusion.UI.Xaml.TreeGrid.Helpers;
+using Syncfusion.Windows.Controls.Cells;
 using Syncfusion.XlsIO;
 using Color = System.Drawing.Color;
 using Environment = System.Environment;
@@ -434,6 +441,139 @@ namespace PRSDesktop
             return item;
         }
 
+        private IEnumerable<DocumentSetNode> GetSelectedNodes()
+        {
+            return treeGrid.SelectedItems
+                .Select(x => x as DocumentSetNode)
+                .NotNull();
+        }
+
+        private RowColumnIndex GetMouseRowColumnIndex(Point? mousePos = null)
+        {
+            var rci = treeGrid.GetTreePanel().PointToCellRowColumnIndex(mousePos ?? Mouse.GetPosition(treeGrid));
+            return new RowColumnIndex(rci.RowIndex, rci.ColumnIndex);
+        }
+
+        private IEnumerable<JobDocumentSetMileStoneBlock> GetSelectedBlocks(Point? mousePos = null)
+        {
+            var rowColumnIndex = GetMouseRowColumnIndex(mousePos);
+            if (rowColumnIndex.IsEmpty)
+            {
+                return Enumerable.Empty<JobDocumentSetMileStoneBlock>();
+            }
+            var treeNodeAtRowIndex = treeGrid.GetNodeAtRowIndex(rowColumnIndex.RowIndex);
+
+            var mappingname = treeGrid.Columns[rowColumnIndex.ColumnIndex].MappingName;
+            var blockkey = mappingname.Replace("Blocks[", "").Replace("]", "");
+            
+            return treeGrid.SelectedItems
+                .Select(x => (x as DocumentSetNode)?.Blocks[blockkey])
+                .Where(x => !x.IsNullOrWhiteSpace())
+                .Select(x => Serialization.Deserialize<JobDocumentSetMileStoneBlock>(x))
+                .NotNull();
+        }
+        private DocumentSetNode? GetHoveredSet(Point? mousePos = null)
+        {
+            var rowColumnIndex = GetMouseRowColumnIndex(mousePos);
+            if (rowColumnIndex.IsEmpty)
+                return null;
+            var treeNodeAtRowIndex = treeGrid.GetNodeAtRowIndex(rowColumnIndex.RowIndex);
+            return treeNodeAtRowIndex.Item as DocumentSetNode;
+        }
+
+        private JobDocumentSetMileStoneBlock? GetHoveredBlock(Point? mousePos = null)
+        {
+            var rowColumnIndex = GetMouseRowColumnIndex(mousePos);
+            if (rowColumnIndex.IsEmpty)
+                return null;
+
+            var mappingname = treeGrid.Columns[rowColumnIndex.ColumnIndex].MappingName;
+            var blockkey = mappingname.Replace("Blocks[", "").Replace("]", "");
+
+            var treeNodeAtRowIndex = treeGrid.GetNodeAtRowIndex(rowColumnIndex.RowIndex);
+            var block = (treeNodeAtRowIndex.Item as DocumentSetNode)?.Blocks[blockkey];
+            if (block.IsNullOrWhiteSpace()) return null;
+
+            return Serialization.Deserialize<JobDocumentSetMileStoneBlock>(block);
+        }
+
+        private bool CanCreateNewMileStone(Guid typeID, Guid[] setIDs)
+        {
+            foreach (var setID in setIDs)
+            {
+                var openmilestones = _milestones.Rows.Any(r =>
+                    Guid.Equals(r.Get<JobDocumentSetMileStone, Guid>(c => c.DocumentSet.ID), setID)
+                    && Guid.Equals(r.Get<JobDocumentSetMileStone, Guid>(c => c.Type.ID), typeID)
+                    && (r.Get<JobDocumentSetMileStone, DateTime>(c => c.Closed).IsEmpty() ||
+                        (r.Get<JobDocumentSetMileStone, JobDocumentSetMileStoneStatus>(c => c.Status) == JobDocumentSetMileStoneStatus.Approved))
+                );
+                if (openmilestones)
+                    return false;
+            }
+            return true;
+        }
+
+        private void Grid_Drop(object sender, System.Windows.DragEventArgs e)
+        {
+            var block = GetHoveredBlock(e.GetPosition(treeGrid));
+            if (block is not null)
+            {
+                var milestoneRow = _milestones.Rows.FirstOrDefault(r => r.Get<JobDocumentSetMileStone, Guid>(c => c.ID) == block.ID);
+                if (milestoneRow is null) return;
+
+                var result = DocumentUtils.HandleFileDrop(e.Data);
+                if (result is null) return;
+
+                UploadFiles(new CoreRow[] { milestoneRow }, result);
+            }
+            else
+            {
+                var rowColumnIndex = GetMouseRowColumnIndex(e.GetPosition(treeGrid));
+                if (rowColumnIndex.IsEmpty) return;
+
+                var mappingname = treeGrid.Columns[rowColumnIndex.ColumnIndex].MappingName;
+
+                var typeID = _types.FirstOrDefault(x => x.Value.Columns.Contains(mappingname)).Key;
+                var setID = GetHoveredSet(e.GetPosition(treeGrid))?.ID ?? Guid.Empty;
+                if (setID == Guid.Empty) return;
+
+                if(CanCreateNewMileStone(typeID, new Guid[] { setID }))
+                {
+                    var result = DocumentUtils.HandleFileDrop(e.Data);
+                    if (result is null) return;
+
+                    var documents = result.Select(x => CreateDocument(x.Item1, x.Item2)).ToList();
+
+                    var milestone = new JobDocumentSetMileStone();
+
+                    milestone.DocumentSet.ID = setID;
+                    milestone.Type.ID = typeID;
+                    milestone.Status = JobDocumentSetMileStoneStatus.NotStarted;
+                    milestone.Due = DateTime.Today;
+
+                    var grid = new JobDocumentSetMileStoneGrid();
+                    if (grid.EditItems(new JobDocumentSetMileStone[] { milestone }))
+                    {
+                        Client.Save(documents, "Uploaded by user.");
+
+                        var files = documents.Select(doc =>
+                        {
+                            var file = new JobDocumentSetMileStoneFile();
+                            file.DocumentLink.ID = doc.ID;
+                            file.EntityLink.ID = milestone.ID;
+                            return file;
+                        });
+                        Client.Save(files, "Uploaded by user.");
+
+                        Refresh();
+                    }
+                }
+                else
+                {
+                    MessageWindow.ShowMessage("Cannot create a new milestone here.", "Error", image: MessageWindow.WarningImage);
+                }
+            }
+        }
 
         private void TreeGrid_OnContextMenuOpening(object sender, ContextMenuEventArgs e)
         {
@@ -445,97 +585,56 @@ namespace PRSDesktop
 
             MileStoneMenu.Items.Clear();
 
-            var tag = (e.OriginalSource as FrameworkElement).Tag;
-            Point pos =  Mouse.GetPosition(treeGrid);
-            var treeGridPanel = this.treeGrid.GetTreePanel();
-            // get the row and column index based on the pointer position 
-            var rowColumnIndex = treeGridPanel.PointToCellRowColumnIndex(pos);
+            var rowColumnIndex = GetMouseRowColumnIndex();
             if (rowColumnIndex.IsEmpty)
                 return;
             var treeNodeAtRowIndex = treeGrid.GetNodeAtRowIndex(rowColumnIndex.RowIndex);                       
  
             if (rowColumnIndex.ColumnIndex < 2)
             {
-
-                var documents = treeGrid.SelectedItems.Select(x => (x as DocumentSetNode)).ToArray();
+                var documents = GetSelectedNodes().ToArray();
                 var ids = documents.Select(x => x.ID).ToArray();
                 
-                MenuItem edit = new MenuItem();
-                edit.Header = "Edit Document Set";
-                edit.Click += (o, args) => { EditDocumentSets(ids); };
-                MileStoneMenu.Items.Add(edit);
+                MileStoneMenu.AddItem("Edit Document Set", null, ids, EditDocumentSets);
                 
                 if (documents.Length == 1)
                 {
-                    MileStoneMenu.Items.Add(new Separator());
-                    MenuItem addchild = new MenuItem();
-                    addchild.Header = "Add Child";
-                    addchild.Click += (o, args) => { AddChildDocument(documents.First()); };
-                    MileStoneMenu.Items.Add(addchild);
+                    MileStoneMenu.AddSeparator();
+                    MileStoneMenu.AddItem("Add Child", null, documents.First(), AddChildDocument);
                 }
 
 
-                MenuItem movetofolder = new MenuItem();
+                var movetofolder = new MenuItem();
                 movetofolder.Header = "Move To Folder";
                 bool hasfolders = PopulateFolders(movetofolder, documents);
                 if (hasfolders)
                 {
-                    MileStoneMenu.Items.Add(new Separator());
+                    MileStoneMenu.AddSeparator();
                     MileStoneMenu.Items.Add(movetofolder);
                 }
 
-                MileStoneMenu.Items.Add(new Separator());
-                MenuItem detailscolumn = new MenuItem();
-                detailscolumn.Header = (treeGrid.Columns[1].Width > 0) ? "Hide Detail Column" : "Show Detail Column";
-                detailscolumn.Click += ShowHideDetailsColumn;
-                MileStoneMenu.Items.Add(detailscolumn);
+                MileStoneMenu.AddSeparator();
+                MileStoneMenu.AddItem((treeGrid.Columns[1].Width > 0) ? "Hide Detail Column" : "Show Detail Column", null, ShowHideDetailsColumn);
                 
                 return;
             }
             
             var mappingname = treeGrid.Columns[rowColumnIndex.ColumnIndex].MappingName;
-            var blockkey = mappingname.Replace("Blocks[", "").Replace("]", "");
-            var typeid = _types.FirstOrDefault(x => x.Value.Columns.Contains(mappingname)).Key;
-
-            //Guid setid = (treeGrid.SelectedItem as DocumentSetNode).ID;
-            Guid[] setids = treeGrid.SelectedItems.Select(x => (x as DocumentSetNode).ID).ToArray();
+            var typeID = _types.FirstOrDefault(x => x.Value.Columns.Contains(mappingname)).Key;
 
-            //Guid.TryParse(tag.ToString(), out Guid milestoneid);
-            var blocks = treeGrid.SelectedItems.Select(x => (x as DocumentSetNode).Blocks[blockkey]).Where(x => !String.IsNullOrWhiteSpace(x))
-                .ToArray();
-            var milestoneids = blocks.Select(x => Serialization.Deserialize<JobDocumentSetMileStoneBlock>(x).ID).ToArray();
+            var setIDs = GetSelectedNodes().Select(x => x.ID).ToArray();
 
-            //var milestone = _milestones.Rows.FirstOrDefault(r => r.Get<JobDocumentSetMileStone, Guid>(c => c.ID) == milestoneid);
-            var milestones = _milestones.Rows.Where(r => milestoneids.Contains(r.Get<JobDocumentSetMileStone, Guid>(c => c.ID))).ToArray();
-            
-            bool canCreateNewMileStones = true;
-            foreach (var setid in setids)
-            {
-                var openmilestones = _milestones.Rows.Any(r =>
-                    Guid.Equals(r.Get<JobDocumentSetMileStone, Guid>(c => c.DocumentSet.ID), setid)
-                    && Guid.Equals(r.Get<JobDocumentSetMileStone, Guid>(c => c.Type.ID), typeid)
-                    && (r.Get<JobDocumentSetMileStone, DateTime>(c => c.Closed).IsEmpty() ||
-                        (r.Get<JobDocumentSetMileStone, JobDocumentSetMileStoneStatus>(c => c.Status) == JobDocumentSetMileStoneStatus.Approved))
-                );
-                if (openmilestones)
-                    canCreateNewMileStones = false;
-                
-            }
+            var milestoneIDs = GetSelectedBlocks().Select(x => x.ID).ToArray();
+            var milestoneRows = _milestones.Rows.Where(r => milestoneIDs.Contains(r.Get<JobDocumentSetMileStone, Guid>(c => c.ID))).ToArray();
             
-            if (canCreateNewMileStones)
+            if (CanCreateNewMileStone(typeID, setIDs))
             {
-                MenuItem newmilestone = new MenuItem()
-                {
-                    Header = "New Milestone",
-                    Tag = typeid
-                };
-                newmilestone.Click += (o, args) => { CreateMileStone(setids, typeid, DateTime.Today); };
-                MileStoneMenu.Items.Add(newmilestone);
+                MileStoneMenu.AddItem("New Milestone", null, () => CreateMileStone(setIDs, typeID, DateTime.Today));
             }
 
-            if (milestones.Any())
+            if (milestoneRows.Any())
             {
-                MenuItem setstatus = new MenuItem() { Header = "Change Status" };
+                var setStatus = MileStoneMenu.AddItem("Change Status", null, null);
                 foreach (JobDocumentSetMileStoneStatus newstatus in Enum.GetValues(typeof(JobDocumentSetMileStoneStatus)))
                 {
                     MenuItem setstatus2 = null;
@@ -549,78 +648,62 @@ namespace PRSDesktop
                         case JobDocumentSetMileStoneStatus.InProgress:
                         case JobDocumentSetMileStoneStatus.OnHold:
                         case JobDocumentSetMileStoneStatus.InfoRequired:
-                            setstatus2 = new MenuItem() { Header = newstatus.ToString().SplitCamelCase() };
-                            setstatus2.Click += (o, args) => { ChangeMileStoneStatus(milestones, newstatus, DateTime.MinValue, DateTime.MinValue); };
+                            setStatus.AddItem(
+                                newstatus.ToString().SplitCamelCase(),
+                                null,
+                                () => ChangeMileStoneStatus(milestoneRows, newstatus, DateTime.MinValue, DateTime.MinValue));
                             break;
 
                         case JobDocumentSetMileStoneStatus.Submitted:
-                            setstatus2 = CreateCalendar(
+                            setStatus.Items.Add(CreateCalendar(
                                 MileStoneMenu,
                                 newstatus.ToString().SplitCamelCase(),
                                 DateTime.Today,
-                                milestones,
-                                (r, t) => { ChangeMileStoneStatus(milestones, newstatus, t, DateTime.MinValue); }
-                            );
+                                milestoneRows,
+                                (r, t) => { ChangeMileStoneStatus(milestoneRows, newstatus, t, DateTime.MinValue); }
+                            ));
                             break;
                             
                         case JobDocumentSetMileStoneStatus.Approved:
                         case JobDocumentSetMileStoneStatus.Cancelled:
                         case JobDocumentSetMileStoneStatus.Rejected:
-                            setstatus2 = CreateCalendar(
+                            setStatus.Items.Add(CreateCalendar(
                                 MileStoneMenu,
                                 newstatus.ToString().SplitCamelCase(),
                                 DateTime.Today,
-                                milestones,
-                                (r, t) => { ChangeMileStoneStatus(milestones, newstatus, null, t); }
-                            );
+                                milestoneRows,
+                                (r, t) => { ChangeMileStoneStatus(milestoneRows, newstatus, null, t); }
+                            ));
                             break;
 
                     }
-                    if (setstatus2 != null)
-                        setstatus.Items.Add(setstatus2);
                 }
-
-                MileStoneMenu.Items.Add(setstatus);
                 
-                MenuItem editmilestone = new MenuItem() { Header = "Edit MileStone" };
-                editmilestone.Click += (o, args) => { EditMileStones(milestones); };
-                MileStoneMenu.Items.Add(editmilestone);
+                MileStoneMenu.AddItem("Edit Milestone", null, milestoneRows, EditMileStones);
 
 
-                //var closed = milestones.Any(r => !r.Get<JobDocumentSetMileStone, DateTime>(c => c.Closed).IsEmpty());
-                if ((setids.Length == 1) && (milestones.Length == 1)) // && !closed)
+                if (setIDs.Length == 1 && milestoneRows.Length == 1)
                 {
-                    
-
-
-                    var attachments = milestones[0].Get<JobDocumentSetMileStone, int>(x => x.Attachments);
+                    var attachments = milestoneRows[0].Get<JobDocumentSetMileStone, int>(x => x.Attachments);
                     if (attachments > 1)
                     {
-                        MenuItem splitmilestone = new MenuItem() { Header = "Split MileStone" };
-                        splitmilestone.Click += (o, args) => { SplitMileStone(setids[0], milestones[0]); };
-                        MileStoneMenu.Items.Add(splitmilestone);
+                        MileStoneMenu.AddItem("Split MileStone", null, () => { SplitMileStone(setIDs[0], milestoneRows[0]); });
                     }
-
-
-
                 }
                 
-                if (milestones.Any())
+                if (milestoneRows.Any())
                 {
+                    MileStoneMenu.AddSeparator();
+                    MileStoneMenu.AddItem(milestoneRows.Length > 1 ? "Upload and Match File Names" : "Upload Files", null, milestoneRows, UploadFiles);
+
+                    var download = MileStoneMenu.AddItem("Download Files", null, null);
                     
-                    MileStoneMenu.Items.Add(new Separator());
-                    
-                    MenuItem upload = new MenuItem() { Header = milestones.Length > 1 ? "Upload and Match File Names" : "Upload Files" };
-                    upload.Click += (o, args) => { UploadFiles(milestones); };
-                    MileStoneMenu.Items.Add(upload);
-                    
-                    MenuItem download = new MenuItem() { Header = "Download Files" };
                     download.Items.Add(new MenuItem());
                     download.SubmenuOpened += (o, e) =>
                     {
                         download.Items.Clear();
                         var files = new Client<JobDocumentSetMileStoneFile>().Query(
-                            new Filter<JobDocumentSetMileStoneFile>(x => x.EntityLink.ID).InList(milestoneids),
+                            new Filter<JobDocumentSetMileStoneFile>(x => x.EntityLink.ID).InList(milestoneIDs),
                             new Columns<JobDocumentSetMileStoneFile>(x => x.ID)
                                 .Add(x => x.DocumentLink.FileName)
                                 .Add(x => x.DocumentLink.ID),
@@ -630,93 +713,118 @@ namespace PRSDesktop
                         {
                             foreach (var row in files.Rows)
                             {
-                                MenuItem downloadone = new MenuItem()
-                                {
-                                    Header = row.Get<JobDocumentSetMileStoneFile, String>(x => x.DocumentLink.FileName),
-                                };
-                                downloadone.Click += (sender, args) =>
-                                {
-                                    DownloadFiles(
-                                        new CoreRow[] { milestones[0] },
-                                        row.Get<JobDocumentSetMileStoneFile, Guid>(x => x.DocumentLink.ID)
-                                    );
-                                };
-                                download.Items.Add(downloadone);
+                                download.AddItem(
+                                    row.Get<JobDocumentSetMileStoneFile, String>(x => x.DocumentLink.FileName),
+                                    null,
+                                    () => DownloadFiles(
+                                        new CoreRow[] { milestoneRows[0] },
+                                        row.Get<JobDocumentSetMileStoneFile, Guid>(x => x.DocumentLink.ID)));
                             }
 
                             if (download.Items.Count > 1)
                             {
-                                download.Items.Add(new Separator());
-
-                                MenuItem downloadall = new MenuItem()
-                                {
-                                    Header = "Download All",
-                                };
-                                downloadall.Click += (sender, args) =>
-                                {
-                                    DownloadFiles(
-                                        milestones,
-                                        Guid.Empty
-                                    );
-                                };
-                                download.Items.Add(downloadall);
+                                download.AddSeparator();
+                                download.AddItem("Download All", null, () => DownloadFiles(milestoneRows, Guid.Empty));
                             }
                         }
                         else
                         {
-                            download.Items.Add(
-                                new MenuItem()
-                                {
-                                    Header = "No Files to download",
-                                    IsEnabled = false
-                                }
-                            );
+                            download.AddItem("No Files to download", null, null, enabled: false);
                         }
                     };
-                    
-                    MileStoneMenu.Items.Add(download);
                 }
-
-                // if ((milestoneids.Length == 1)) // && !closed)
-                // {
-                //     MenuItem managefiles = new MenuItem()
-                //     {
-                //         Header = "Manage Files"
-                //     };
-                //     managefiles.Click += (sender, args) => { ManageFiles(milestones[0]); };
-                //     MileStoneMenu.Items.Add(managefiles);
-                // }
                 
-                MileStoneMenu.Items.Add(new Separator());
-                MenuItem export = new MenuItem { Header = "Export Files" };
-                export.Click += (o, args) => ExportFiles(milestones);
-                MileStoneMenu.Items.Add(export);
+                MileStoneMenu.AddSeparator();
+                MileStoneMenu.AddItem("Export Files", null, milestoneRows, ExportFiles);
 
-                if(milestoneids.Length == 1)
+                if(milestoneIDs.Length == 1)
                 {
                     MileStoneMenu.AddSeparatorIfNeeded();
                     var item = MileStoneMenu.AddItem("Forms", PRSDesktop.Resources.kanban, null);
                     DynamicGridUtils.PopulateFormMenu<JobDocumentSetMileStoneForm, JobDocumentSetMileStone, JobDocumentSetMileStoneLink>(
                         item,
-                        milestoneids[0],
-                        () => new Client<JobDocumentSetMileStone>().Load(new Filter<JobDocumentSetMileStone>(x => x.ID).IsEqualTo(milestoneids[0])).First());
+                        milestoneIDs[0],
+                        () => new Client<JobDocumentSetMileStone>().Load(new Filter<JobDocumentSetMileStone>(x => x.ID).IsEqualTo(milestoneIDs[0])).First(),
+                        editOnAdd: true,
+                        customiseEditor: (editor) =>
+                        {
+                            if (Security.IsAllowed<CanPrintReports>())
+                            {
+                                editor.CustomButtons.Add(new DynamicFormEditButton("Attach", AttachForm));
+                            }
+                        });
                 }
 
-                MileStoneMenu.Items.Add(new Separator());
-                MenuItem delete = new MenuItem { Header = "Delete MileStone" };
-                delete.Click += (o, args) => DeleteMileStone(milestones);
-                MileStoneMenu.Items.Add(delete);
+                MileStoneMenu.AddSeparator();
+                MileStoneMenu.AddItem("Delete MileStone", null, milestoneRows, DeleteMileStone);
             }
 
             if (MileStoneMenu.Items.Count == 0)
                 e.Handled = true;
+        }
+
+        private void AttachForm(DynamicFormEditWindow window, DynamicFormEditButton button)
+        {
+            var dataModel = window.DataModel;
+            if(dataModel is null)
+            {
+                return;
+            }
+
+            var menu = new ContextMenu();
+
+            var model = new DigitalFormReportDataModel<JobDocumentSetMileStoneForm>(
+                new Filter<JobDocumentSetMileStoneForm>(x => x.ID).IsEqualTo(dataModel.Instance.ID),
+                dataModel.Instance.Form.ID);
+            model.AddFormData(dataModel.Instance.ID, window.SaveValues().ToLoadStorage());
 
+            var reports = ReportUtils.LoadReports(dataModel.Instance.Form.ID.ToString(), model).Where(x => x.Visible).ToList();
+            if(reports.Count == 1)
+            {
+                AttachReport((reports[0], model, dataModel));
+                return;
+            }
+            else if(reports.Count > 1)
+            {
+                foreach (var report in reports)
+                {
+                    menu.AddItem(report.Name, null, (report, model, dataModel), AttachReport);
+                }
+            }
+            if(menu.Items.Count == 0)
+            {
+                menu.AddItem("No reports", null, null, enabled: false);
+            }
+            menu.IsOpen = true;
         }
 
-        private void ExportFiles(CoreRow[] milestones)
+        private void AttachReport((ReportTemplate report, DigitalFormReportDataModel<JobDocumentSetMileStoneForm> model, IDigitalFormDataModel dataModel) arg)
         {
+            Progress.ShowModal("Please wait", (progress) =>
+            {
+                var (report, model, dataModel) = arg;
+                var data = ReportUtils.ReportToPDF(report, model);
+
+                var document = new Document
+                {
+                    Data = data,
+                    FileName = $"Digital Form - {report.Name} - {DateTime.Now:yyyy-MM-dd hh:mm:ss.fff}.pdf",
+                    CRC = CoreUtils.CalculateCRC(data),
+                    TimeStamp = DateTime.Now
+                };
+                Client.Save(document, $"Generated from form report {report.Name}");
+
+                var file = new JobDocumentSetMileStoneFile();
+                file.DocumentLink.ID = document.ID;
+                file.EntityLink.ID = dataModel.Entity.ID;
+                Client.Save(file, "Attached from form.");
+            });
+            Refresh();
+        }
 
-            SaveFileDialog sfd = new SaveFileDialog();
+        private void ExportFiles(CoreRow[] milestones)
+        {
+            var sfd = new SaveFileDialog();
             sfd.Filter = "Compressed Files (*.zip)|*.zip";
             sfd.AddExtension = true;
             if (sfd.ShowDialog() != DialogResult.OK)
@@ -775,7 +883,7 @@ namespace PRSDesktop
             MessageBox.Show("All Done!");
         }
 
-        private void ShowHideDetailsColumn(object sender, RoutedEventArgs e)
+        private void ShowHideDetailsColumn()
         {
             _settings.DetailsVisible = !_settings.DetailsVisible;
             new UserConfiguration<JobDocumentSetTreeSettings>().Save(_settings);
@@ -784,7 +892,7 @@ namespace PRSDesktop
 
         private bool PopulateFolders(MenuItem menu, IEnumerable<DocumentSetNode> documents)
         {
-            CoreTable data = new Client<JobDocumentSetFolder>().Query(
+            var data = Client.Query(
                 new Filter<JobDocumentSetFolder>(x => x.Job.ID).IsEqualTo(Job.ID),
                 new Columns<JobDocumentSetFolder>(x => x.ID)
                     .Add(x => x.Parent.ID)
@@ -792,29 +900,28 @@ namespace PRSDesktop
             );
             if (!data.Rows.Any())
                 return false;
-            CoreTreeNodes folders = new CoreTreeNodes();
+
+            var folders = new CoreTreeNodes();
             folders.Load<JobDocumentSetFolder>(data, x => x.ID, x => x.Parent.ID, x => x.Name);
+
             foreach (var folder in folders.Nodes)
                 DoPopulateFolder(menu, folder, documents);
+
             return true;
         }
 
         private void DoPopulateFolder(MenuItem header, CoreTreeNode folder, IEnumerable<DocumentSetNode> documents)
         {
-            MenuItem menu = new MenuItem();
-            menu.Header = folder.Description;
-            menu.Click += (sender, args) => MoveToFolder(documents, folder);
-            header.Items.Add(menu);
+            var menu = header.AddItem(folder.Description, null, () => MoveToFolder(documents, folder));
             foreach (var childfolder in folder.Children)
                 DoPopulateFolder(menu, childfolder, documents);
         }
 
         private void MoveToFolder(IEnumerable<DocumentSetNode> documents, CoreTreeNode folder)
         {
-            
             using (new WaitCursor())
             {
-                List<JobDocumentSet> updates = new List<JobDocumentSet>();
+                var updates = new List<JobDocumentSet>();
                 foreach (var document in documents)
                 {
                     var folderid = Data.Rows.FirstOrDefault(r => r.Get<JobDocumentSet, Guid>(c => c.ID) == document.ID)?.Get<JobDocumentSet, Guid>(c => c.Folder.ID) ?? Guid.Empty;
@@ -840,11 +947,7 @@ namespace PRSDesktop
 
         private void SplitMileStone(Guid setid, CoreRow milestone)
         {
-            if (MessageBox.Show(
-                    "Are you sure you wish to split this Document Set?", 
-                    "Confirm Delete", 
-                    MessageBoxButton.YesNo
-                ) != MessageBoxResult.Yes)
+            if (!MessageWindow.ShowYesNo("Are you sure you wish to split this Document Set?", "Confirm Split"))
                 return;
             
             Guid milestoneid = milestone.Get<JobDocumentSetMileStone, Guid>(c => c.ID);
@@ -907,24 +1010,6 @@ namespace PRSDesktop
             
         }
 
-        // private void ManageFiles(CoreRow milestone)
-        // {
-        //     var grid = new JobDocumentSetMileStoneFileGrid();
-        //     grid.OnGetWaterMark += (row) => milestone.Get<JobDocumentSetMileStone, String>(c => c.Watermark);
-        //     grid.ShowSupercededColumn = false;
-        //     Window window = new Window();
-        //     window.Padding = new Thickness(5);
-        //     window.Content = grid;
-        //     window.Width = 300;
-        //     window.Height = 500;
-        //     window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
-        //     grid.Load(milestone.ToObject<JobDocumentSetMileStone>(), null);
-        //     grid.Margin = new Thickness(5);
-        //     grid.Refresh(true, true);
-        //     window.ShowDialog();
-        //     Refresh();
-        // }
-
         private void DownloadFiles(CoreRow[] rows, Guid id)
         {
             
@@ -967,7 +1052,7 @@ namespace PRSDesktop
             }
         }
         
-        private bool SelectFiles(out String[] files)
+        private bool SelectFiles(out string[] files)
         {
             Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
             dlg.Filter = "PDF Files (*.pdf)|*.pdf";
@@ -981,125 +1066,186 @@ namespace PRSDesktop
             return false;
         }
 
-        private void UploadFiles(CoreRow[] rows)
+        private Document CreateDocument(string file, Stream? stream)
         {
-            if (rows?.Length < 1)
+            var filename = Path.GetFileName(file).ToLower();
+
+            Document doc;
+
+            if (Path.GetExtension(filename) != ".pdf")
             {
-                MessageBox.Show("No Rows Selected");
-                return;
+                filename = Path.ChangeExtension(filename, ".pdf");
+                if (stream is null)
+                {
+                    var data = DataEntryReGroupWindow.RenderToPDF(file).SaveToBytes();
+                    doc = new Document
+                    {
+                        Data = data,
+                        FileName = filename,
+                        CRC = CoreUtils.CalculateCRC(data),
+                        TimeStamp = new FileInfo(filename).LastWriteTime
+                    };
+                }
+                else
+                {
+                    var data = DataEntryReGroupWindow.RenderToPDF(file, stream).SaveToBytes();
+                    doc = new Document
+                    {
+                        Data = data,
+                        FileName = filename,
+                        CRC = CoreUtils.CalculateCRC(data),
+                        TimeStamp = DateTime.Now
+                    };
+                }
             }
-            
-            if (SelectFiles(out String[] filenames))
+            else if (stream is null)
+            {
+                doc = Document.FromFile(file);
+                doc.FileName = filename;
+            }
+            else
             {
+                byte[] data;
+                using (var ms = new MemoryStream())
+                {
+                    stream.CopyTo(ms);
+                    data = ms.ToArray();
+                }
+                doc = new Document
+                {
+                    Data = data,
+                    FileName = filename,
+                    CRC = CoreUtils.CalculateCRC(data),
+                    TimeStamp = new FileInfo(file).LastWriteTime
+                };
+            }
 
-                Dictionary<String, Guid>? setlookups = rows.Length > 1
-                    ? new Dictionary<string, Guid>(
-                        rows.Select(
-                            r => new KeyValuePair<String, Guid>(
-                                r.Get<JobDocumentSetMileStone, String>(c => c.DocumentSet.Code),
-                                r.Get<JobDocumentSetMileStone, Guid>(c => c.ID)
-                            )
+            return doc;
+        }
+
+        private void UploadFiles(CoreRow[] rows, IEnumerable<Tuple<string, Stream?>> fileStreams)
+        {
+            var files = fileStreams.AsArray();
+
+            var setlookups = rows.Length > 1
+                ? new Dictionary<string, Guid>(
+                    rows.Select(
+                        r => new KeyValuePair<String, Guid>(
+                            r.Get<JobDocumentSetMileStone, String>(c => c.DocumentSet.Code),
+                            r.Get<JobDocumentSetMileStone, Guid>(c => c.ID)
                         )
                     )
-                    : null;
+                )
+                : null;
 
-                if ((setlookups != null) && (rows.Length > 1))
+            if ((setlookups != null) && (rows.Length > 1))
+            {
+                var unmatched = files.Where(x => !setlookups.Keys.Any(key => Path.GetFileName(x.Item1).ToLower().ToLower().StartsWith(key.ToLower())));
+                if (unmatched.Any())
                 {
-                    var unmatched = filenames.Where(filename => !setlookups.Keys.Any(key => Path.GetFileName(filename).ToLower().ToLower().StartsWith(key.ToLower())));
-                    if (unmatched.Any())
-                    {
-                        MessageBox.Show("Unable to match the following files:\n" + String.Join("\n", unmatched));
-                        return;
-                    }
+                    MessageBox.Show("Unable to match the following files:\n" + String.Join("\n", unmatched));
+                    return;
                 }
+            }
 
-                var milestoneids = rows.Select(r => r.Get<JobDocumentSetMileStone, Guid>(c => c.ID)).ToArray();
-                var table = new Client<JobDocumentSetMileStoneFile>().Query(
-                    new Filter<JobDocumentSetMileStoneFile>(x => x.EntityLink.ID).InList(milestoneids),
-                    new Columns<JobDocumentSetMileStoneFile>(x => x.ID, x => x.DocumentLink.ID)
-                        .Add(x => x.DocumentLink.FileName)
-                );
-                List<JobDocumentSetMileStoneFile> jobdocsetfiles = new List<JobDocumentSetMileStoneFile>();
-                foreach (var row in table.Rows)
-                { 
-                    jobdocsetfiles.Add(row.ToObject<JobDocumentSetMileStoneFile>());
-                }
-                var currentfiles = table.ToDictionary<JobDocumentSetMileStoneFile, String, Guid>(x => x.DocumentLink.FileName, x => x.DocumentLink.ID);
-                var matched = filenames.Where(filename => currentfiles.Keys.Any(key => Path.GetFileName(filename).ToLower().StartsWith(key.ToLower())));
-                bool replace = false;
-                if (matched.Any())
-                {
-                    var confirm = MessageBox.Show(
-                        "The following files already exist!\n\n Do you wish to replace them?\n\n" + String.Join("\n", matched),
-                        "Replace Files",
-                        MessageBoxButton.YesNoCancel);
-                    if (confirm == MessageBoxResult.Cancel)
-                        return;
-                    replace = confirm == MessageBoxResult.Yes;
-                }
+            var milestoneids = rows.Select(r => r.Get<JobDocumentSetMileStone, Guid>(c => c.ID)).ToArray();
+
+            var jobdocsetfiles = Client.Query(
+                new Filter<JobDocumentSetMileStoneFile>(x => x.EntityLink.ID).InList(milestoneids),
+                new Columns<JobDocumentSetMileStoneFile>(x => x.ID, x => x.DocumentLink.ID)
+                    .Add(x => x.DocumentLink.FileName))
+                .ToObjects<JobDocumentSetMileStoneFile>().ToList();
 
-                int doccount = 0;
-                Progress.ShowModal("Uploading Files", (progress) =>
+            var currentfiles = jobdocsetfiles.ToDictionary(x => x.DocumentLink.FileName.ToLower(), x => x.DocumentLink.ID);
+
+            var matched = files.Select(x => x.Item1).Where(x => currentfiles.ContainsKey(Path.GetFileName(x).ToLower()));
+
+            var replace = false;
+            if (matched.Any())
+            {
+                var result = MessageWindow.New()
+                    .Title("Replace Files")
+                    .Message("The following files already exist!\n\n Do you wish to replace them?\n\n" + String.Join("\n", matched))
+                    .AddYesButton("Replace Files")
+                    .AddNoButton("Keep Existing")
+                    .AddCancelButton()
+                    .Display().Result;
+                if (result == MessageWindowResult.Cancel) return;
+                replace = result == MessageWindowResult.Yes;
+            }
+
+            int doccount = 0;
+            Progress.ShowModal("Uploading Files", (progress) =>
+            {
+                var documents = new List<Document>();
+                var newDocuments = new List<Document>();
+                var linked = new List<JobDocumentSetMileStoneFile>();
+                foreach (var (file, stream) in files)
                 {
-                    List<Document> documents = new List<Document>();
-                    List<JobDocumentSetMileStoneFile> linked = new List<JobDocumentSetMileStoneFile>();
-                    foreach (var file in filenames)
+                    var filename = Path.GetFileName(file).ToLower();
+                    if (replace || !matched.Contains(filename))
                     {
-                        if (!matched.Contains(file) || replace)
+                        var doc = CreateDocument(file, stream);
+                        documents.Add(doc);
+                        if (currentfiles.TryGetValue(filename, out var docID))
                         {
-                            var filename = Path.GetFileName(file).ToLower();
-                            var code = currentfiles.Keys.FirstOrDefault(key => filename.StartsWith(key.ToLower()));
-                            var doc = new Document();
-                            doc.ID = String.IsNullOrWhiteSpace(code) ? Guid.Empty : currentfiles[code];
-                            doc.CommitChanges();
-                            doc.Data = File.ReadAllBytes(file);
-                            doc.FileName = filename;
-                            doc.CRC = CoreUtils.CalculateCRC(doc.Data);
-                            doc.TimeStamp = new FileInfo(file).LastWriteTime;
-                            documents.Add(doc);
-
-                            if (doc.ID != Guid.Empty)
+                            doc.SetObserving(false);
+                            doc.ID = docID;
+                            doc.SetObserving(true);
+
+                            var linkedfile = jobdocsetfiles.FirstOrDefault(x => x.DocumentLink.ID == doc.ID);
+                            if (linkedfile != null)
                             {
-                                var linkedfile = jobdocsetfiles.FirstOrDefault(x => x.DocumentLink.ID == doc.ID);
-                                linkedfile.DocumentLink.ID = Guid.Empty;
-                                linkedfile.CommitChanges();
                                 linkedfile.DocumentLink.ID = doc.ID;
+                                // Regenerate the thumbnail
+                                linkedfile.Thumbnail = null;
                                 linked.Add(linkedfile);
-                            }                           
+                            }
+                        }
+                        else
+                        {
+                            newDocuments.Add(doc);
                         }
                     }
-                    if (documents.Any())
-                        new Client<Document>().Save(documents.ToArray(), "Uploaded by User");
+                }
+                Client.Save(documents, "Uploaded by User");
 
-                    if (linked.Any())
-                        new Client<JobDocumentSetMileStoneFile>().Save(linked, "Uploaded by User");
+                progress.Report("Updating Links");
 
-                    progress.Report("Updating Links");
-                    List<JobDocumentSetMileStoneFile> links = new List<JobDocumentSetMileStoneFile>();
-                    foreach (var document in documents)
+                foreach (var document in newDocuments)
+                {
+                    var link = new JobDocumentSetMileStoneFile();
+                    if (setlookups != null)
                     {
-                        if (!currentfiles.Any(x => x.Value == document.ID))
-                        {
-                            var link = new JobDocumentSetMileStoneFile();
-                            if (setlookups != null)
-                            {
-                                var filename = Path.GetFileName(document.FileName).ToLower();
-                                var code = setlookups.Keys.FirstOrDefault(key => filename.StartsWith(key.ToLower()));
-                                link.EntityLink.ID = setlookups[code];
-                            }
-                            else
-                                link.EntityLink.ID = rows.First().Get<JobDocumentSetMileStone, Guid>(c => c.ID);
-
-                            link.DocumentLink.ID = document.ID;
-                            links.Add(link);
-                        }
+                        var filename = Path.GetFileName(document.FileName).ToLower();
+                        var code = setlookups.Keys.FirstOrDefault(key => filename.StartsWith(key.ToLower()));
+                        link.EntityLink.ID = setlookups[code];
                     }
-                    if (links.Any())
-                        new Client<JobDocumentSetMileStoneFile>().Save(links, "Uploaded By User");
-                    doccount = documents.Count;
-                });
-                MessageBox.Show(String.Format("{0} Files Uploaded", doccount > 0 ? doccount : "No"));
-                Refresh();
+                    else
+                        link.EntityLink.ID = rows.First().Get<JobDocumentSetMileStone, Guid>(c => c.ID);
+
+                    link.DocumentLink.ID = document.ID;
+                    linked.Add(link);
+                }
+                Client.Save(linked, "Uploaded by User");
+
+                doccount = documents.Count;
+            });
+            MessageWindow.ShowMessage(string.Format("{0} Files Uploaded", doccount > 0 ? doccount : "No"), "Success");
+            Refresh();
+        }
+
+        private void UploadFiles(CoreRow[] rows)
+        {
+            if (rows.Length < 1)
+            {
+                MessageBox.Show("No Rows Selected");
+                return;
+            }
+            
+            if (SelectFiles(out String[] filenames))
+            {
+                UploadFiles(rows, filenames.Select(x => new Tuple<string, Stream?>(x, null)));
             }
         }
 
@@ -1194,9 +1340,10 @@ namespace PRSDesktop
                         return result;
                     }
                     return null;
-                }, true)
-            )
+                }, true))
+            {
                 Refresh();
+            }
         }
 
         private void ChangeMileStoneStatus(CoreRow[] rows, JobDocumentSetMileStoneStatus newstatus, DateTime? issued, DateTime? closed)
@@ -1345,7 +1492,7 @@ namespace PRSDesktop
 
             
         }
-        
+
         #endregion
 
         #region Button Bar Actions

+ 3 - 3
prs.desktop/Panels/Products/Master List/ProductsPanel.xaml.cs

@@ -299,7 +299,7 @@ namespace PRSDesktop
 
                 Progress.Show("Updating Images: " + Path.GetFileName(filename));
 
-                Bitmap bmp = null;
+                Bitmap? bmp = null;
                 if (Path.GetExtension(filename).ToLower().Equals(".dxf"))
                     bmp = DxfUtils.DXFToBitmap(filename);
                 else
@@ -345,7 +345,7 @@ namespace PRSDesktop
 
             var id = Guid.Empty;
             var filename = "";
-            Bitmap bitmap = null;
+            Bitmap? bitmap = null;
 
             if (Clipboard.ContainsData("ProductImage"))
             {
@@ -364,7 +364,7 @@ namespace PRSDesktop
                 {
                     var list = Clipboard.GetFileDropList();
                     if (list.Count > 0)
-                        filename = Path.ChangeExtension(Path.GetFileName(list[0]), ".png");
+                        filename = Path.ChangeExtension(Path.GetFileName(list[0]), ".png") ?? "";
                 }
 
                 //filename = String.Format("clip{0:yyyyMMddhhmmss}.png",DateTime.Now);

+ 27 - 18
prs.server/Forms/ServerGrid.cs

@@ -148,8 +148,9 @@ public class ServerGrid : DynamicGrid<Server>
 
     private void CreateServerMenu(DynamicMenuColumn column, CoreRow? row)
     {
-        if (row == null)
+        if (row is null)
             return;
+
         var status = ServerMenuStatus(row);
         if (status == DynamicMenuStatus.Hidden)
             return;
@@ -159,7 +160,7 @@ public class ServerGrid : DynamicGrid<Server>
         column.AddItem(
             "View Console",
             Properties.Resources.target,
-            (row) => StartConsole(row, key),
+            (row) => StartConsole(row!, key),
             null,
             status == DynamicMenuStatus.Enabled && !_consoles.ContainsKey(key)
         );
@@ -170,14 +171,14 @@ public class ServerGrid : DynamicGrid<Server>
             column.AddItem(
                 "Custom Fields",
                 Properties.Resources.service,
-                (row) => EditCustomFields(row), 
+                (row) => EditCustomFields(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
             column.AddItem(
                 "Database Scripts", 
                 Properties.Resources.script, 
-                (row) => EditDatabaseScripts(row), 
+                (row) => EditDatabaseScripts(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
@@ -185,7 +186,7 @@ public class ServerGrid : DynamicGrid<Server>
             column.AddItem(
                 "Update License", 
                 Properties.Resources.key,
-                (row) => UpdateDatabaseLicense(row), 
+                (row) => UpdateDatabaseLicense(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
@@ -193,7 +194,7 @@ public class ServerGrid : DynamicGrid<Server>
             column.AddItem(
                 "Manage Deletions", 
                 Properties.Resources.delete,
-                (row) => ManageDeletions(row), 
+                (row) => ManageDeletions(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
@@ -204,21 +205,21 @@ public class ServerGrid : DynamicGrid<Server>
             column.AddItem(
                 "Edit Templates", 
                 Properties.Resources.script,
-                (row) => EditWebTemplates(row), 
+                (row) => EditWebTemplates(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
             column.AddItem(
                 "Edit Styles", 
                 Properties.Resources.css,
-                (row) => EditWebStyles(row), 
+                (row) => EditWebStyles(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
             column.AddItem(
                 "Edit Documents", 
                 Properties.Resources.pdf,
-                (row) => EditWebDocuments(row), 
+                (row) => EditWebDocuments(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
@@ -226,7 +227,7 @@ public class ServerGrid : DynamicGrid<Server>
             column.AddItem(
                 "PRS Mobile Settings",
                 Properties.Resources.web,
-                (row) => PRSMobileSettings(row), 
+                (row) => PRSMobileSettings(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
@@ -237,7 +238,7 @@ public class ServerGrid : DynamicGrid<Server>
             column.AddItem(
                 "Scheduled Scripts", 
                 Properties.Resources.script,
-                (row) => EditScheduledScripts(row), 
+                (row) => EditScheduledScripts(row!), 
                 null, 
                 status == DynamicMenuStatus.Enabled
             );
@@ -343,9 +344,12 @@ public class ServerGrid : DynamicGrid<Server>
                     {
                         var key = certrow.Get<Server, string>(x => x.Key);
                         var certservice = GetService(key);
-                        certservice.Refresh();
-                        if (certservice.Status == ServiceControllerStatus.Running)
-                            testcertificates = true;
+                        if(certservice is not null)
+                        {
+                            certservice.Refresh();
+                            if (certservice.Status == ServiceControllerStatus.Running)
+                                testcertificates = true;
+                        }
                     }
 
                     foreach (var row in table.Rows)
@@ -354,6 +358,11 @@ public class ServerGrid : DynamicGrid<Server>
                         var key = row.Get<Server, string>(x => x.Key);
                         
                         var service = GetService(key);
+                        if(service is null)
+                        {
+                            continue;
+                        }
+
                         _serviceStatuses.TryGetValue(key, out var oldstatus);
                         service.Refresh();
                         _serviceStatuses[key] = service.Status;
@@ -365,8 +374,8 @@ public class ServerGrid : DynamicGrid<Server>
                             var type = row.Get<Server, ServerType>(x => x.Type);
                             if (type == ServerType.Database)
                             {
-                                _secureConnections.TryGetValue(key, out bool oldsecure);
-                                if (!_pipemonitors.TryGetValue(key, out RpcClientPipeTransport client))
+                                _secureConnections.TryGetValue(key, out var oldsecure);
+                                if (!_pipemonitors.TryGetValue(key, out var client))
                                 {
                                     client = new RpcClientPipeTransport(DatabaseServerProperties.GetPipeName(key, true));
                                     client.Connect();
@@ -383,8 +392,8 @@ public class ServerGrid : DynamicGrid<Server>
                             else if (type == ServerType.Web)
                             {
                                 var props = row.ToObject<Server>().Properties as WebServerProperties;
-                                _secureConnections.TryGetValue(key, out bool oldsecure);
-                                if (!_webmonitors.TryGetValue(key, out HttpClient client))
+                                _secureConnections.TryGetValue(key, out var oldsecure);
+                                if (!_webmonitors.TryGetValue(key, out var client))
                                 {
                                     client = new HttpClient { BaseAddress = new Uri($"https://127.0.0.1:{props.ListenPort}") };
                                     _webmonitors[key] = client;