using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; using com.sun.tools.javac.file; using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using net.sf.mpxj.phoenix.schema.phoenix5; 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 JobDocumentSetFolder = Comal.Classes.JobDocumentSetFolder; using MessageBox = System.Windows.MessageBox; using UserControl = System.Windows.Controls.UserControl; namespace PRSDesktop { public class JobDocumentSetTreeSettings : UserConfigurationSettings { public bool DetailsVisible { get; set; } public JobDocumentSetTreeSettings() { DetailsVisible = true; } } public delegate void JobDocumentSetMileStoneSelected(JobDocumentSetMileStoneBlock block); public partial class JobDocumentSetTree : UserControl { public event JobDocumentSetMileStoneSelected MileStoneSelected; private struct MileStone { public Guid TypeID { get; set; } public CoreRow Row { get; set; } } private struct MileStoneType { public String Code { get; set; } public String Description { get; set; } public Dictionary> SetMileStones { get; set; } public List Columns { get; set; } } public Guid JobID { get; set; } public Guid[] FolderIDs{ get; set; } //public bool DisciplineVisible { get; set; } public Guid DisciplineID { get; set; } //public bool TypeVisible { get; set; } public Guid TypeID { get; set; } //public bool CategoryVisible { get; set; } public Guid CategoryID { get; set; } //public bool AreaVisible { get; set; } public Guid AreaID { get; set; } public String SearchText { get; set; } private Dictionary _types = null; private CoreTable _milestones = null; public CoreTable Data { get; private set; } = null; private CoreTable _files = null; private bool _hidesuperceded = false; private bool _flatlist = false; private bool _includeretired = false; private DocumentSetNodes _documentsets = null; private JobDocumentSetTreeSettings _settings; public JobDocumentSetTree() { InitializeComponent(); AddImage.Source = PRSDesktop.Resources.add.AsBitmapImage(); EditImage.Source = PRSDesktop.Resources.pencil.AsBitmapImage(); DeleteImage.Source = PRSDesktop.Resources.delete.AsBitmapImage(); _settings = new UserConfiguration().Load(); treeGrid.Loaded += (o, e) => { treeGrid.GetTreePanel().RowHeights[1] = 0; treeGrid.UpdateDataRow(1); }; } public void Refresh() { using (new WaitCursor()) { var scrollviewer = WPFUtils.FindVisualChildren(treeGrid).FirstOrDefault(); var verticalOffset = scrollviewer != null ? scrollviewer.VerticalOffset : 0; var horizontalOffset = treeGrid.SelectedItem != null ? scrollviewer.HorizontalOffset : 0; treeGrid.ItemsSource = null; var setfilter = new Filter(x => x.Job.ID).IsEqualTo(JobID); if ((FolderIDs?.Any() == true) && !FolderIDs.Contains(CoreUtils.FullGuid)) setfilter = setfilter.And(x => x.Folder.ID).InList(FolderIDs); if (DisciplineID != Guid.Empty) setfilter = setfilter.And(x => x.Discipline.ID).IsEqualTo(DisciplineID); if (TypeID != Guid.Empty) setfilter = setfilter.And(x => x.Type.ID).IsEqualTo(TypeID); if (CategoryID != Guid.Empty) setfilter = setfilter.And(x => x.Category.ID).IsEqualTo(CategoryID); if (AreaID != Guid.Empty) setfilter = setfilter.And(x => x.Area.ID).IsEqualTo(AreaID); if (!_includeretired) setfilter = setfilter.And(x => x.Retired).IsEqualTo(DateTime.MinValue); if (!String.IsNullOrWhiteSpace(SearchText)) setfilter = setfilter.TextSearch(SearchText, x => x.Code, x => x.Description); MultiQuery query = new MultiQuery(); query.Add( setfilter, new Columns(x => x.ID) .Add(x => x.Parent.ID) .Add(x => x.Code) .Add(x => x.Description) .Add(x => x.Date) .Add(x => x.Size) .Add(x => x.Scale) .Add(x => x.Employee.Name) .Add(x=>x.Folder.ID) .Add(x=>x.Discipline.Description) .Add(x=>x.Category.Description) .Add(x=>x.Type.Description) .Add(x=>x.Area.Description), new SortOrder(x => x.Code) ); var milestonefilter = new Filter(x => x.DocumentSet.Job.ID).IsEqualTo(JobID); if ((FolderIDs?.Any() == true) && !FolderIDs.Contains(CoreUtils.FullGuid)) milestonefilter = milestonefilter.And(x => x.DocumentSet.Folder.ID).InList(FolderIDs); query.Add( milestonefilter, new Columns(x => x.ID) .Add(x => x.DocumentSet.ID) .Add(x => x.DocumentSet.Code) .Add(x => x.Type.ID) .Add(x => x.Type.Code) .Add(x => x.Status) .Add(x => x.Notes) .Add(x => x.Revision) .Add(x => x.Due) .Add(x => x.Submitted) .Add(x => x.Closed) .Add(x => x.Attachments) .Add(x => x.Watermark) ); if (_types == null) { query.Add( null, new Columns(x => x.ID) .Add(x => x.Code) .Add(x => x.Description), new SortOrder(x => x.Sequence) ); } query.Query(); Data = query.Get(); _milestones = query.Get(); if (_types == null) { _types = query.Get().ToDictionary( x => x.ID, r => new MileStoneType() { Code = r.Get(c => c.Code), Description = r.Get(c => c.Description), SetMileStones = new Dictionary>(), Columns = new List() } ); } else { foreach (var typeid in _types.Keys) { _types[typeid].Columns.Clear(); _types[typeid].SetMileStones.Clear(); } } var milestones = _milestones.ToLookup( x => x.DocumentSet.ID, r => new MileStone() { TypeID = r.Get(c => c.Type.ID), Row = r } ); foreach (var milestone in milestones) { foreach (var entry in milestone) { if (!_types[entry.TypeID].SetMileStones.ContainsKey(milestone.Key)) _types[entry.TypeID].SetMileStones[milestone.Key] = new List(); if (_hidesuperceded) _types[entry.TypeID].SetMileStones[milestone.Key].Clear(); _types[entry.TypeID].SetMileStones[milestone.Key].Add(entry.Row); } } List columns = new List(); foreach (var typeid in _types.Keys) { int count = 1; foreach (var setkey in _types[typeid].SetMileStones.Keys) count = Math.Max(count, _types[typeid].SetMileStones[setkey].Count); for (int i = 1; i <= count; i++) { String column = String.Format("{0}_{1}", _types[typeid].Code, i); columns.Add(column); _types[typeid].Columns.Add(String.Format("Blocks[{0}]", column)); } } _documentsets = new DocumentSetNodes(columns); foreach (var setrow in Data.Rows) { Guid setid = setrow.Get(x => x.ID); Guid parentid = _flatlist ? Guid.Empty : setrow.Get(x => x.Parent.ID); String code = setrow.Get(c => c.Code); String description = setrow.Get(c => c.Description); var tags = new List() { setrow.Get(c => c.Discipline.Description), setrow.Get(c => c.Type.Description), setrow.Get(c => c.Category.Description), setrow.Get(c => c.Area.Description) }.Where(x=>!String.IsNullOrWhiteSpace(x)).Distinct().ToArray(); var node = _documentsets.Add(setid, parentid); JobDocumentSetDescriptionBlock desc = new JobDocumentSetDescriptionBlock( setid, code, description, tags); node.Description = Serialization.Serialize(desc); JobDocumentSetDetailsBlock dets = new JobDocumentSetDetailsBlock() { ID = setid, Date = setrow.Get(c => c.Date), Size = setrow.Get(c => c.Size), Scale = setrow.Get(c => c.Scale), Employee = setrow.Get(c => c.Employee.Name) }; node.Details = Serialization.Serialize(dets); foreach (var typeid in _types.Keys) { if (_types[typeid].SetMileStones.TryGetValue(setid, out var rows)) { int i = 1; foreach (var row in rows) { JobDocumentSetMileStoneBlock block = new JobDocumentSetMileStoneBlock(); block.ID = row.Get(c => c.ID); block.Revision = row.Get(c => c.Revision); block.Status = row.Get(c => c.Status); block.Date = (block.Status == JobDocumentSetMileStoneStatus.Approved) || (block.Status == JobDocumentSetMileStoneStatus.Cancelled) || (block.Status == JobDocumentSetMileStoneStatus.Rejected) ? row.Get(c => c.Closed) : block.Status == JobDocumentSetMileStoneStatus.Submitted ? block.Date = row.Get(c => c.Submitted) : row.Get(c => c.Due); String[] notes = row.Get(c => c.Notes); block.Notes = notes != null ? String.Join("\n", notes) : ""; block.Attachments = row.Get(c => c.Attachments); block.Watermark = row.Get(c => c.Watermark); node.Blocks[String.Format("{0}_{1}", _types[typeid].Code, i)] = Serialization.Serialize(block); i++; } } } } ConfigureColumns(_documentsets); ConfigureStackedHeader(); treeGrid.ItemsSource = _documentsets.Nodes; DocumentCount.Content = $"{_documentsets.Nodes.Count} {(_documentsets.Nodes.Count > 1 ? "Records" : "Record")}"; if (scrollviewer != null) { scrollviewer.ScrollToVerticalOffset(verticalOffset); scrollviewer.ScrollToHorizontalOffset(horizontalOffset); } } } #region Grid Configuration private void ConfigureColumns(DocumentSetNodes documentsets) { treeGrid.Columns.Clear(); treeGrid.Columns.Add(new TreeGridTemplateColumn() { CellTemplate = FindResource("descriptionTemplate") as DataTemplate, MappingName = "Description", SetCellBoundValue = true, MinimumWidth = 250, ColumnSizer = TreeColumnSizer.Star }); treeGrid.Columns.Add(new TreeGridTemplateColumn() { CellTemplate = FindResource("detailsTemplate") as DataTemplate, MappingName = "Details", SetCellBoundValue = true, Width = _settings.DetailsVisible ? 120 : 0 }); foreach (var column in documentsets.Columns) { var col = new TreeGridTemplateColumn() { CellTemplate = FindResource("milestoneTemplate") as DataTemplate, MappingName = String.Format("Blocks[{0}]",column), SetCellBoundValue = true, HeaderText = " ", Width = 80, ShowToolTip = true }; treeGrid.Columns.Add(col); } } private void ConfigureStackedHeader() { stackedHeaderRow.StackedColumns.Clear(); stackedHeaderRow.StackedColumns.Add(new StackedColumn() { ChildColumns = "Description,Details", HeaderText = "Document Register" }); foreach (var typeid in _types.Keys) { stackedHeaderRow.StackedColumns.Add(new StackedColumn() { ChildColumns = String.Join(",", _types[typeid].Columns), HeaderText = _types[typeid].Code }); } } private void TreeGrid_OnItemsSourceChanged(object? sender, TreeGridItemsSourceChangedEventArgs e) { var panel = treeGrid.GetTreePanel(); panel.RowHeights[1] = 0; } private void TreeGrid_OnNodeCollapsing(object? sender, NodeCollapsingEventArgs e) { e.Cancel = true; } public MenuItem CreateCalendar(ContextMenu menu, string text, DateTime startDate, CoreRow[] milestones, Action? action) { var item = new MenuItem(); var calendarItem = new MenuItem(); var calendar = new System.Windows.Controls.Calendar { DisplayDate = startDate, SelectedDate = null}; calendar.SelectedDatesChanged += (o, e) => { action?.Invoke(milestones, calendar.SelectedDate); menu.IsOpen = false; }; calendarItem.Header = calendar; calendarItem.Style = DynamicGridUtils.Resources["NonHighlightMenuItem"] as Style; item.Header = text; item.Items.Add(calendarItem); item.IsCheckable = false; return item; } private void TreeGrid_OnContextMenuOpening(object sender, ContextMenuEventArgs e) { if (treeGrid.SelectedItem == null) { e.Handled = true; return; } 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); 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 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); 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); } MenuItem movetofolder = new MenuItem(); movetofolder.Header = "Move To Folder"; bool hasfolders = PopulateFolders(movetofolder, documents); if (hasfolders) { MileStoneMenu.Items.Add(new Separator()); 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); 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(); //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(x).ID).ToArray(); //var milestone = _milestones.Rows.FirstOrDefault(r => r.Get(c => c.ID) == milestoneid); var milestones = _milestones.Rows.Where(r => milestoneids.Contains(r.Get(c => c.ID))).ToArray(); bool canCreateNewMileStones = true; foreach (var setid in setids) { var openmilestones = _milestones.Rows.Any(r => Guid.Equals(r.Get(c => c.DocumentSet.ID), setid) && Guid.Equals(r.Get(c => c.Type.ID), typeid) && (r.Get(c => c.Closed).IsEmpty() || (r.Get(c => c.Status) == JobDocumentSetMileStoneStatus.Approved)) ); if (openmilestones) canCreateNewMileStones = false; } if (canCreateNewMileStones) { MenuItem newmilestone = new MenuItem() { Header = "New Milestone", Tag = typeid }; newmilestone.Click += (o, args) => { CreateMileStone(setids, typeid, DateTime.Today); }; MileStoneMenu.Items.Add(newmilestone); } if (milestones.Any()) { MenuItem setstatus = new MenuItem() { Header = "Change Status" }; foreach (JobDocumentSetMileStoneStatus newstatus in Enum.GetValues(typeof(JobDocumentSetMileStoneStatus))) { MenuItem setstatus2 = null; switch (newstatus) { case JobDocumentSetMileStoneStatus.Unknown: break; case JobDocumentSetMileStoneStatus.NotStarted: 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); }; break; case JobDocumentSetMileStoneStatus.Submitted: setstatus2 = CreateCalendar( MileStoneMenu, newstatus.ToString().SplitCamelCase(), DateTime.Today, milestones, (r, t) => { ChangeMileStoneStatus(milestones, newstatus, t, DateTime.MinValue); } ); break; case JobDocumentSetMileStoneStatus.Approved: case JobDocumentSetMileStoneStatus.Cancelled: case JobDocumentSetMileStoneStatus.Rejected: setstatus2 = CreateCalendar( MileStoneMenu, newstatus.ToString().SplitCamelCase(), DateTime.Today, milestones, (r, t) => { ChangeMileStoneStatus(milestones, 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); //var closed = milestones.Any(r => !r.Get(c => c.Closed).IsEmpty()); if ((setids.Length == 1) && (milestones.Length == 1)) // && !closed) { var attachments = milestones[0].Get(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); } } if (milestones.Any()) { 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().Query( new Filter(x => x.EntityLink.ID).InList(milestoneids), new Columns(x => x.ID) .Add(x => x.DocumentLink.FileName) .Add(x => x.DocumentLink.ID), new SortOrder(x => x.DocumentLink.FileName) ); if (files.Rows.Any()) { foreach (var row in files.Rows) { MenuItem downloadone = new MenuItem() { Header = row.Get(x => x.DocumentLink.FileName), }; downloadone.Click += (sender, args) => { DownloadFiles( new CoreRow[] { milestones[0] }, row.Get(x => x.DocumentLink.ID) ); }; download.Items.Add(downloadone); } 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); } } else { download.Items.Add( new MenuItem() { Header = "No Files to download", IsEnabled = 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.Items.Add(new Separator()); MenuItem delete = new MenuItem { Header = "Delete MileStone" }; delete.Click += (o, args) => DeleteMileStone(milestones); MileStoneMenu.Items.Add(delete); } if (MileStoneMenu.Items.Count == 0) e.Handled = true; } private void ExportFiles(CoreRow[] milestones) { SaveFileDialog sfd = new SaveFileDialog(); sfd.Filter = "Compressed Files (*.zip)|*.zip"; sfd.AddExtension = true; if (sfd.ShowDialog() != DialogResult.OK) return; Progress.ShowModal("Exporting Files", (progress) => { progress.Report("Getting File Links"); var milestoneids = milestones.Select(r => r.Get(c => c.ID)).ToArray(); var links = new Client().Query( new Filter(c => c.EntityLink.ID).InList(milestoneids), new Columns(x => x.EntityLink.ID) .Add(x=>x.EntityLink.DocumentSet.Code) .Add(x=>x.EntityLink.Type.Code) .Add(x=>x.EntityLink.Revision) .Add(x=>x.EntityLink.Status) .Add(x => x.DocumentLink.ID) .Add(x=>x.DocumentLink.FileName) ); Syncfusion.Compression.Zip.ZipArchive zip = new Syncfusion.Compression.Zip.ZipArchive(); int i = 0; foreach (var row in links.Rows) { i++; String code = row.Get(c => c.EntityLink.DocumentSet.Code); String filename = Path.GetFileNameWithoutExtension(row.Get(c => c.DocumentLink.FileName)); String extension = Path.GetExtension(row.Get(c => c.DocumentLink.FileName)); String type = $"{row.Get(c=>c.EntityLink.Type.Code)} {row.Get(c=>c.EntityLink.Revision)}".Trim(); var status = row.Get(c => c.EntityLink.Status); filename = $"{code}/{filename} {type} ({status}){extension}"; progress.Report($"Processing {i} of {links.Rows.Count} files"); Guid docid = row.Get(c => c.DocumentLink.ID); var data = new Client().Query( new Filter(x => x.ID).IsEqualTo(docid), new Columns(x=>x.ID).Add(x => x.Data) ).Rows.Select(r=>r.Get(c=>c.Data)).FirstOrDefault(); if (data != null) { var item = new ZipArchiveItem(zip, filename, new MemoryStream(data), true, FileAttributes.Normal); zip.AddItem(item); } } progress.Report("Closing archive"); zip.Save(sfd.FileName); zip.Close(); }); MessageBox.Show("All Done!"); } private void ShowHideDetailsColumn(object sender, RoutedEventArgs e) { _settings.DetailsVisible = !_settings.DetailsVisible; new UserConfiguration().Save(_settings); treeGrid.Columns[1].Width = _settings.DetailsVisible ? 120: 0; } private bool PopulateFolders(MenuItem menu, IEnumerable documents) { CoreTable data = new Client().Query( new Filter(x => x.Job.ID).IsEqualTo(JobID), new Columns(x => x.ID) .Add(x => x.Parent.ID) .Add(x => x.Name) ); if (!data.Rows.Any()) return false; DynamicTreeNodes folders = new DynamicTreeNodes(); folders.Load(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, DynamicTreeNode folder, IEnumerable documents) { MenuItem menu = new MenuItem(); menu.Header = folder.Description; menu.Click += (sender, args) => MoveToFolder(documents, folder); header.Items.Add(menu); foreach (var childfolder in folder.Children) DoPopulateFolder(menu, childfolder, documents); } private void MoveToFolder(IEnumerable documents, DynamicTreeNode folder) { using (new WaitCursor()) { List updates = new List(); foreach (var document in documents) { var folderid = Data.Rows.FirstOrDefault(r => r.Get(c => c.ID) == document.ID)?.Get(c => c.Folder.ID) ?? Guid.Empty; if (folderid != folder.ID) { var update = new JobDocumentSet(); update.ID = document.ID; update.CommitChanges(); update.Folder.ID = folder.ID; update.Parent.ID = Guid.Empty; updates.Add(update); } } if (updates.Any()) new Client().Save(updates, "Moved to Folder: " + folder.Description); else MessageBox.Show("Nothing to Do!"); } Refresh(); } 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) return; Guid milestoneid = milestone.Get(c => c.ID); var dlg = new MultiSelectDialog( new Filter(c => c.EntityLink.ID).IsEqualTo(milestoneid), null, true ); if (dlg.ShowDialog() == true) { var files = dlg.Items(); Progress.ShowModal("Splitting Document Set", (progress) => { JobDocumentSet newset = new Client().Query( new Filter(x => x.ID).IsEqualTo(setid) ).Rows.FirstOrDefault()?.ToObject(); if (newset != null) { newset.ID = Guid.Empty; newset.CommitChanges(); newset.Parent.ID = setid; newset.Code = String.Format("{0} (COPY)", newset.Code); //newset.Description = "New Child"; new Client().Save(newset, "Created by Splitting MileStone"); progress.Report("Creating Milestone"); JobDocumentSetMileStone newms = new Client().Query( new Filter(c=>c.ID).IsEqualTo(milestoneid) ).Rows.FirstOrDefault()?.ToObject(); if (newms != null) { newms.ID = Guid.Empty; newset.CommitChanges(); newms.DocumentSet.ID = newset.ID; new Client().Save(newms, "Created By Splitting MileStone"); progress.Report("Moving Files"); foreach (var file in files) file.EntityLink.ID = newms.ID; new Client().Save(files, "Moved when Splitting MileStone"); } } }); Refresh(); } } private void AddChildDocument(DocumentSetNode node) { if (node == null) return; var folderid = Data.Rows.FirstOrDefault(r => r.Get(c => c.ID) == node.ID)?.Get(c => c.Folder.ID) ?? Guid.Empty; JobDocumentSet newset = new JobDocumentSet(); newset.Parent.ID = node.ID; newset.Job.ID = JobID; newset.Folder.ID = folderid; var grid = new DynamicDataGrid(); if (grid.EditItems(new[] { newset })) Refresh(); } // private void ManageFiles(CoreRow milestone) // { // var grid = new JobDocumentSetMileStoneFileGrid(); // grid.OnGetWaterMark += (row) => milestone.Get(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(), null); // grid.Margin = new Thickness(5); // grid.Refresh(true, true); // window.ShowDialog(); // Refresh(); // } private void DownloadFiles(CoreRow[] rows, Guid id) { FolderBrowserDialog dlg = new FolderBrowserDialog(); if (dlg.ShowDialog() == DialogResult.OK) { Progress.ShowModal("Downloading Files", (progress) => { foreach (var row in rows) { var status = row.Get(c => c.Status); var stage = row.Get(c => c.Type.Code); var revision = row.Get(c => c.Revision); String tag = String.Format(" - {0}{1} ({2})", stage, String.IsNullOrWhiteSpace(revision) ? "" : " - Rev " + revision, status.ToString().SplitCamelCase()); var filter = id == Guid.Empty ? new Filter(x => x.ID).InQuery( new Filter(x => x.EntityLink.ID).IsEqualTo( row.Get(c => c.ID)), x => x.DocumentLink.ID ) : new Filter(x => x.ID).IsEqualTo(id); var files = new Client().Query(filter); foreach (var filerow in files.Rows) { string filename = filerow.Get(c => c.FileName); string extension = Path.GetExtension(filename); string basefilename = Path.GetFileNameWithoutExtension(filename); filename = String.Format("{0}{1}{2}", basefilename, tag, extension); filename = Path.Combine(dlg.SelectedPath, filename); File.WriteAllBytes(filename, filerow.Get(c => c.Data)); } } }); Process.Start(new ProcessStartInfo(dlg.SelectedPath) { UseShellExecute = true }); } } private bool SelectFiles(out String[] files) { Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); dlg.Filter = "PDF Files (*.pdf)|*.pdf"; dlg.Multiselect = true; if ((dlg.ShowDialog() == true) && (dlg.FileNames.Length > 0)) { files = dlg.FileNames.ToArray(); return true; } files = null; return false; } private void UploadFiles(CoreRow[] rows) { if (rows?.Length < 1) { MessageBox.Show("No Rows Selected"); return; } if (SelectFiles(out String[] filenames)) { Dictionary? setlookups = rows.Length > 1 ? new Dictionary( rows.Select( r => new KeyValuePair( r.Get(c => c.DocumentSet.Code), r.Get(c => c.ID) ) ) ) : null; if ((setlookups != null) && (rows.Length > 1)) { 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; } } var milestoneids = rows.Select(r => r.Get(c => c.ID)).ToArray(); var currentfiles = new Client().Query( new Filter(x => x.EntityLink.ID).InList(milestoneids), new Columns(x => x.DocumentLink.ID) .Add(x => x.DocumentLink.FileName) ).ToDictionary(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; } int doccount = 0; Progress.ShowModal("Uploading Files", (progress) => { List documents = new List(); foreach (var file in filenames) { if (!matched.Contains(file) || replace) { 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 (documents.Any()) new Client().Save(documents.ToArray(), "Uploaded by User"); progress.Report("Updating Links"); List links = new List(); foreach (var document in documents) { 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(c => c.ID); link.DocumentLink.ID = document.ID; links.Add(link); } } if (links.Any()) new Client().Save(links, "Uploaded By User"); doccount = documents.Count; }); MessageBox.Show(String.Format("{0} Files Uploaded", doccount > 0 ? doccount : "No")); Refresh(); } } private Dictionary GetPreviousMileStones(Guid[] setids, Guid typeid) { var result = new Dictionary(); foreach (var setid in setids) { var typeindex = _types.Keys.IndexOf(typeid); JobDocumentSetMileStone? last = null; while ((last == null) && (typeindex > 0)) { last = _milestones.Rows.LastOrDefault(r => (r.Get(c => c.DocumentSet.ID) == setid) && (r.Get(c => c.Type.ID) == _types.Keys.ToArray()[typeindex])) ?.ToObject(); typeindex--; } if (last != null) result[setid] = last; } return result; } private void CreateMileStone(Guid[] setids, Guid typeid, DateTime duedate) { bool bCopy = false; var lastmilestones = GetPreviousMileStones(setids, typeid); if (lastmilestones.Any(x => x.Value.Attachments > 0)) { var confirm = MessageBox.Show("Do you wish to copy the files from the previous milestones?", "Copy Files", MessageBoxButton.YesNoCancel); if (confirm == MessageBoxResult.Cancel) return; bCopy = confirm == MessageBoxResult.Yes; } Dictionary updates = new Dictionary(); foreach (var setid in setids) { JobDocumentSetMileStone milestone = new JobDocumentSetMileStone(); milestone.DocumentSet.ID = setid; milestone.Type.ID = typeid; milestone.Status = JobDocumentSetMileStoneStatus.NotStarted; milestone.Due = duedate; JobDocumentSetMileStoneFile[] files = new JobDocumentSetMileStoneFile[] { }; if (bCopy && lastmilestones.TryGetValue(setid, out var lastmilestone)) { if (lastmilestone.Attachments > 0) { files = new Client().Query( new Filter(x => x.EntityLink.ID).InList(lastmilestone.ID), new Columns(x=>x.EntityLink.DocumentSet.ID) .Add(x => x.DocumentLink.FileName) .Add(x => x.DocumentLink.ID), new SortOrder(x => x.DocumentLink.FileName) ).Rows.Select(r=>r.ToObject()).ToArray(); } } updates[milestone] = files; } var grid = new JobDocumentSetMileStoneGrid(); grid.OnAfterSave += (editor, items) => { if (updates.Keys.Count == 1) return; List fileupdates = new List(); foreach (var milestone in updates.Keys) { foreach (var file in updates[milestone]) { file.EntityLink.ID = milestone.ID; fileupdates.Add(file); } } if (fileupdates.Any()) new Client().Save(fileupdates,""); }; if (grid.EditItems(updates.Keys.ToArray(), (t) => { if ((t == typeof(JobDocumentSetMileStoneFile)) && (updates.Keys.Count == 1)) { CoreTable result = new CoreTable(); result.LoadColumns(typeof(JobDocumentSetMileStoneFile)); result.LoadRows(updates[updates.Keys.First()]); return result; } return null; }, true) ) Refresh(); } private void ChangeMileStoneStatus(CoreRow[] rows, JobDocumentSetMileStoneStatus newstatus, DateTime? issued, DateTime? closed) { var milestones = rows.Select(r=>r.ToObject()).ToArray(); foreach (var milestone in milestones) { if (issued.HasValue) milestone.Submitted = issued.Value; if (closed.HasValue) milestone.Closed = closed.Value; milestone.Status = newstatus; } using (new WaitCursor()) new Client().Save(milestones, "Changed Status to " + newstatus.ToString().SplitCamelCase()); Refresh(); } private void EditMileStones(CoreRow[] rows) { var ids = rows.Select(r => r.Get(x => x.ID)).ToArray(); var milestones = new Client().Query( new Filter(x => x.ID).InList(ids) ).Rows.Select(r=>r.ToObject()).ToArray(); var grid = new JobDocumentSetMileStoneGrid(); if (grid.EditItems(milestones)) Refresh(); } private void DeleteMileStone(CoreRow[] rows) { var milestones = rows.Select(r=>r.ToObject()).ToArray(); using (new WaitCursor()) new Client().Delete(milestones,"Deleted by User"); Refresh(); } private void TreeGrid_OnCellToolTipOpening(object? sender, TreeGridCellToolTipOpeningEventArgs e) { var column = e.Column.MappingName.Replace("Blocks[","").Replace("]",""); var data = (e.Record as DocumentSetNode).Blocks[column]; if (String.IsNullOrWhiteSpace(data)) return; var block = Serialization.Deserialize(data.ToString()); Guid id = block.ID; TextBlock text = new TextBlock(); if (!String.IsNullOrWhiteSpace(block.Notes)) { text.Inlines.Add(new Run("Milestone Notes\n") { FontWeight = FontWeights.Bold, TextDecorations = TextDecorations.Underline }); text.Inlines.Add(new Run(block.Notes.Replace("=","").Replace("\n\n","\n")) { FontStyle = FontStyles.Italic }); } if (block.Attachments > 0) { if (!String.IsNullOrWhiteSpace(block.Notes)) text.Inlines.Add(new Run("\n\n")); text.Inlines.Add(new Run("Uploaded Files") { FontWeight = FontWeights.Bold, TextDecorations = TextDecorations.Underline }); var files = new Client().Query( new Filter(x => x.EntityLink.ID).IsEqualTo(block.ID), new Columns(x => x.DocumentLink.FileName), new SortOrder(x => x.DocumentLink.FileName) ); foreach (var row in files.Rows) text.Inlines.Add(new Run("\n"+row.Get(c=>c.DocumentLink.FileName)) { FontStyle = FontStyles.Italic }); } if (!text.Inlines.Any()) { e.ToolTip.Template = null; return; } e.ToolTip.Template = TemplateGenerator.CreateControlTemplate( typeof(System.Windows.Controls.ToolTip), () => { var border = new Border { BorderBrush = new SolidColorBrush(Colors.Gray), BorderThickness = new Thickness(0.75), CornerRadius = new CornerRadius(5), Background = new SolidColorBrush(Colors.LightYellow), Padding = new Thickness(5), Child = text }; return border; } ); } #endregion #region Button Bar Actions private void AddTypes(MenuItem parent, Action addfunction) { if(_types.Count == 0) { MenuItem item = new MenuItem() { Header = "No Document Milestones", IsEnabled = false }; parent.Items.Add(item); } else { foreach (var type in _types.Keys) { MenuItem item = new MenuItem() { Header = _types[type].Description, Tag = type }; item.Click += (o, e) => addfunction(type); parent.Items.Add(item); } } } private void Add_OnClick(object sender, RoutedEventArgs e) { if (FolderIDs?.Any() != true) { MessageBox.Show("Please choose a Folder first!"); return; } else if(FolderIDs.First() == CoreUtils.FullGuid) { MessageBox.Show("Cannot add items to this folder."); return; } ContextMenu menu = new ContextMenu(); var onetoone = new MenuItem() { Header = "Add Individual Files" }; AddTypes(onetoone, AddOneToOneFiles); menu.Items.Add(onetoone); var manytoone = new MenuItem() { Header = "Add Sets of Files" }; AddTypes(manytoone, AddManyToOneFiles); menu.Items.Add(manytoone); menu.Items.Add(new Separator()); var manual = new MenuItem() { Header = "Add Document Set Manually" }; manual.Click += (o, e) => { AddDocumentSet(); }; menu.Items.Add(manual); menu.IsOpen = true; } private void AddOneToOneFiles(Guid type) { Guid folderid = FolderIDs?.FirstOrDefault() ?? Guid.Empty; if (!SelectFiles(out String[] filenames)) return; Progress.ShowModal("Preparing Upload", (progress) => { Dictionary> map = new Dictionary>(); foreach (var filename in filenames) { var data = File.ReadAllBytes(filename); Document document = new Document() { FileName = Path.GetFileName(filename).ToLower(), Data = data, CRC = CoreUtils.CalculateCRC(data), TimeStamp = new FileInfo(filename).LastWriteTime }; JobDocumentSet set = new JobDocumentSet(); set.Job.ID = JobID; set.Folder.ID = folderid; set.Code = Path.GetFileNameWithoutExtension(filename).ToUpper(); set.Description = Path.GetFileNameWithoutExtension(filename).ToUpper(); set.Discipline.ID = DisciplineID; set.Type.ID = TypeID; set.Category.ID = CategoryID; set.Area.ID = AreaID; JobDocumentSetMileStone milestone = new JobDocumentSetMileStone(); milestone.Type.ID = type; milestone.Status = JobDocumentSetMileStoneStatus.InProgress; milestone.Due = DateTime.Today; JobDocumentSetMileStoneFile file = new JobDocumentSetMileStoneFile(); map[filename] = new Tuple( document, set, milestone, file ); } progress.Report("Uploading Files"); var docs = map.Select(x => x.Value.Item1); new Client().Save(docs, "Uploaded By File Selection"); progress.Report("Creating Document Sets"); var sets = map.Select(x => x.Value.Item2); new Client().Save(sets, "Uploaded by File Selection"); progress.Report("Creating MileStones"); foreach (var key in map.Keys) map[key].Item3.DocumentSet.ID = map[key].Item2.ID; var milestones = map.Select(x => x.Value.Item3); new Client().Save(milestones, "Uploaded by File Selection"); progress.Report("Linking Documents"); foreach (var key in map.Keys) { map[key].Item4.EntityLink.ID = map[key].Item3.ID; map[key].Item4.DocumentLink.ID = map[key].Item1.ID; } var files = map.Select(x => x.Value.Item4); new Client().Save(files, "Uploaded by File Selection"); }); MessageBox.Show(String.Format("{0} Document Sets Created", filenames.Length)); Refresh(); } private void AddManyToOneFiles(Guid type) { Guid folderid = FolderIDs?.FirstOrDefault() ?? Guid.Empty; if (!SelectFiles(out String[] filenames)) return; JobDocumentSet set = new JobDocumentSet(); set.Job.ID = JobID; set.Folder.ID = folderid; set.Discipline.ID = DisciplineID; set.Type.ID = TypeID; set.Category.ID = CategoryID; set.Area.ID = AreaID; var grid = new DynamicDataGrid(); grid.OnAfterSave += (form, items) => { Progress.ShowModal("Creating MileStone", (progress) => { JobDocumentSetMileStone milestone = new JobDocumentSetMileStone(); milestone.DocumentSet.ID = set.ID; milestone.Type.ID = type; milestone.Status = JobDocumentSetMileStoneStatus.InProgress; milestone.Due = DateTime.Today; new Client().Save(milestone, "Uploaded By File Selection"); progress.Report("Uploading Files"); List documents = new List(); foreach (var filename in filenames) { var data = File.ReadAllBytes(filename); Document document = new Document() { FileName = Path.GetFileName(filename).ToLower(), Data = data, CRC = CoreUtils.CalculateCRC(data), TimeStamp = new FileInfo(filename).LastWriteTime }; documents.Add(document); new Client().Save(documents, "Uploaded by File Selection"); } progress.Report("Creating File Links"); List files = new List(); foreach (var document in documents) { JobDocumentSetMileStoneFile file = new JobDocumentSetMileStoneFile(); file.EntityLink.ID = milestone.ID; file.DocumentLink.ID = document.ID; files.Add(file); } new Client().Save(files, "Uploaded by File Selection"); }); }; if (grid.EditItems(new[] { set })) { MessageBox.Show(String.Format("{0} files uploaded", filenames.Length)); Refresh(); } } private void AddDocumentSet() { Guid folderid = FolderIDs?.FirstOrDefault() ?? Guid.Empty; JobDocumentSet set = new JobDocumentSet(); set.Job.ID = JobID; set.Folder.ID = folderid; set.Discipline.ID = DisciplineID; set.Type.ID = TypeID; set.Category.ID = CategoryID; set.Area.ID = AreaID; var grid = new DynamicDataGrid(); if (grid.EditItems(new[] { set })) Refresh(); } private void Edit_OnClick(object sender, RoutedEventArgs e) { if (treeGrid.SelectedItem == null) { MessageBox.Show("Please choose a Document Set first"); return; } Guid[] setIDs = treeGrid.SelectedItems.Select(x => (x as DocumentSetNode).ID).ToArray(); EditDocumentSets(setIDs); } private void EditDocumentSets(Guid[] setIDs) { var sets = new Client().Query( new Filter(x => x.ID).InList(setIDs) ).Rows.Select(x => x.ToObject()).ToArray(); var grid = new DynamicDataGrid(); // grid.OnCustomiseEditor += (form, items, column, editor) => // { // if (String.Equals(column.ColumnName, "Discipline.ID")) // editor.Editable = DisciplineVisible ? Editable.Enabled : Editable.Hidden; // if (String.Equals(column.ColumnName, "Type.ID")) // editor.Editable = TypeVisible ? Editable.Enabled : Editable.Hidden; // if (String.Equals(column.ColumnName, "Category.ID")) // editor.Editable = CategoryVisible ? Editable.Enabled : Editable.Hidden; // if (String.Equals(column.ColumnName, "Area.ID")) // editor.Editable = AreaVisible ? Editable.Enabled : Editable.Hidden; // }; if (grid.EditItems(sets)) UpdateNodes(sets); } private void UpdateNodes(IEnumerable sets) { if (_documentsets == null) return; foreach (var set in sets) { var node = _documentsets.GetNode(set.ID); if (node != null) { var tags = new List() { set.Discipline.Description, set.Type.Description, set.Category.Description, set.Area.Description }.Where(x=>!String.IsNullOrWhiteSpace(x)).Distinct().ToArray(); JobDocumentSetDescriptionBlock desc = new JobDocumentSetDescriptionBlock( set.ID, set.Code, set.Description, tags); node.Description = Serialization.Serialize(desc); JobDocumentSetDetailsBlock dets = new JobDocumentSetDetailsBlock() { ID = set.ID, Date = set.Date, Size = set.Size, Scale = set.Scale, Employee = set.Employee.Name }; node.Details = Serialization.Serialize(dets); } } } private void HideRejected_OnClick(object sender, RoutedEventArgs e) { _hidesuperceded = !_hidesuperceded; HideSupercededLabel.Content = _hidesuperceded ? "Show All" : "Last Only"; Refresh(); } private void Delete_OnClick(object sender, RoutedEventArgs e) { if ((treeGrid.SelectedItems == null) || !treeGrid.SelectedItems.Any()) { MessageBox.Show("Please choose a Document Set first"); return; } if (MessageBox.Show( "Are you sure you wish to delete the selected Document Sets?", "Confirm Delete", MessageBoxButton.YesNo ) != MessageBoxResult.Yes) return; List updates = new List(); List orphans = new List(); var items = treeGrid.SelectedItems.Select(x => (DocumentSetNode)x).ToArray(); foreach (DocumentSetNode item in items) { var children = item.Children.Where(x => !items.Contains(x)); if (children.Any()) orphans.AddRange(children); } if (orphans.Any()) { var confirm = MessageBox.Show( "These Document Sets contain children!\nDo you wish to delete these as well?", "Delete Children", MessageBoxButton.YesNoCancel ); if (confirm == MessageBoxResult.Cancel) return; if (confirm == MessageBoxResult.No) { foreach (var orphan in orphans) { var update = new JobDocumentSet(); update.ID = orphan.ID; update.Parent.ID = Guid.Empty; updates.Add(update); } return; } } Progress.ShowModal("Deleting Document Set",(progress) => { if (updates.Any()) new Client().Save(updates, "Parent Document Deleted"); var deletes = items.Select(x=>new JobDocumentSet() { ID = x.ID }).ToArray(); new Client().Delete(deletes, "Deleted By User"); }); Refresh(); } #endregion private void FlatList_OnClick(object sender, RoutedEventArgs e) { _flatlist = !_flatlist; FlatListLabel.Content = _flatlist ? "Tree View" : "Flat List"; Refresh(); } private void IncludeRetired_OnClick(object sender, RoutedEventArgs e) { _includeretired = !_includeretired; FlatListLabel.Content = _includeretired ? "Active Only" : "Include Retired"; Refresh(); } private void TreeGrid_OnSelectionChanged(object? sender, GridSelectionChangedEventArgs e) { //var treeColumn = treeGrid.Columns[e.CurrentRowColumnIndex.ColumnIndex]; //var column = treeColumn.MappingName.Replace("Blocks[","").Replace("]",""); // var column = e.Column.MappingName.Replace("Blocks[","").Replace("]",""); // var data = (e.Record as DocumentSetNode).Blocks[column]; // if (String.IsNullOrWhiteSpace(data)) // return; // // var block = Serialization.Deserialize(data.ToString()); // Guid id = block.ID; } private void TreeGrid_OnCurrentCellActivated(object? sender, CurrentCellActivatedEventArgs e) { var node = treeGrid.CurrentItem as DocumentSetNode; if (node == null) return; var treeColumn = treeGrid.Columns[e.CurrentRowColumnIndex.ColumnIndex]; var column = treeColumn.MappingName.Replace("Blocks[","").Replace("]",""); if (!node.Blocks.ContainsKey(column)) MileStoneSelected(null); else { var block = Serialization.Deserialize(node.Blocks[column]); MileStoneSelected?.Invoke(block); } } private void TreeGrid_OnCellDoubleTapped(object? sender, TreeGridCellDoubleTappedEventArgs e) { var set = e.Record as DocumentSetNode; if (set != null) EditDocumentSets(new Guid[] { set.ID }); } } }