using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using PRSDesktop.Panels.DataEntry; using Syncfusion.Pdf; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Windows; using System.Windows.Media.Imaging; namespace PRSDesktop; public class DataEntryCache : DocumentCache { public override TimeSpan MaxAge => TimeSpan.FromDays(new UserConfiguration().Load().CacheAge); private static DataEntryCache? _cache; public static DataEntryCache Cache { get { _cache ??= DocumentCaches.GetOrRegister(); return _cache; } } public DataEntryCache(): base(nameof(DataEntryCache)) { } } public class DataEntryGrid : DynamicDataGrid { private List? _tags; public DataEntryGrid() { HiddenColumns.Add(x => x.Tag.ID); HiddenColumns.Add(x => x.Tag.AppliesTo); HiddenColumns.Add(x => x.Document.ID); HiddenColumns.Add(x => x.Document.FileName); HiddenColumns.Add(x => x.EntityID); HiddenColumns.Add(x => x.Archived); HiddenColumns.Add(x => x.Note); ActionColumns.Add(new DynamicImageColumn(LinkedImage) { Position = DynamicActionColumnPosition.Start }); var tagFilter = new Filter(x => x.Tag.ID).InList(GetVisibleTags().Select(x => x.ID).ToArray()); if (Security.IsAllowed()) { tagFilter.Or(x => x.Tag.ID).IsEqualTo(Guid.Empty); } var docs = Client.Query( new Filter(x => x.Archived).IsEqualTo(DateTime.MinValue) .And(tagFilter), Columns.None().Add(x => x.Document.ID)); DataEntryCache.Cache.ClearOld(); DataEntryCache.Cache.EnsureStrict(docs.Rows.Select(x => x.Get(x => x.Document.ID)).ToArray()); } private static readonly BitmapImage link = PRSDesktop.Resources.link.AsBitmapImage(); private BitmapImage? LinkedImage(CoreRow? arg) { return arg == null ? link : arg.Get(x => x.EntityID) != Guid.Empty ? link : null; } protected override void DoReconfigure(DynamicGridOptions options) { base.DoReconfigure(options); options.Clear(); options.FilterRows = true; options.MultiSelect = true; options.DragSource = true; options.DragTarget = true; options.SelectColumns = true; } public static List GetVisibleTagList() { var tags = new Client().Query().ToObjects().ToList(); var tagsList = new List(); foreach (var tag in tags) { var entity = CoreUtils.GetEntityOrNull(tag.AppliesTo); if (entity is null || Security.CanView(entity)) { var tagHasEmployee = new Client() .Query( new Filter(x => x.Tag.ID).IsEqualTo(tag.ID) .And(x => x.Employee.ID).IsEqualTo(App.EmployeeID), Columns.None().Add(x => x.ID)) .Rows.Any(); if (tagHasEmployee) { tagsList.Add(tag); } } } return tagsList; } private List GetVisibleTags() { _tags ??= GetVisibleTagList(); return _tags; } /// /// Gets the currently selected tag ID, if all selected rows belong to the same tag. /// /// private Guid GetSelectedTagID() { var tagID = Guid.Empty; foreach (var row in SelectedRows) { var rowTag = row.Get(x => x.Tag.ID); if (tagID == Guid.Empty) { tagID = rowTag; } else if (rowTag != tagID) { return Guid.Empty; } } return tagID; } private IEnumerable>> ExplodeDocuments() { var dataEntryDocs = SelectedRows.ToArray(); var docIDs = dataEntryDocs.Select(r => r.Document.ID).ToArray(); var docs = new Client() .Query( new Filter(x => x.ID).InList(docIDs), Columns.None().Add(x => x.ID).Add(x => x.Data).Add(x => x.FileName)) .ToObjects().ToDictionary(x => x.ID, x => x); foreach (var dataEntryDoc in dataEntryDocs) { if (docs.TryGetValue(dataEntryDoc.Document.ID, out var doc)) { var ms = new MemoryStream(doc.Data); var pdfDoc = DataEntryReGroupWindow.RenderToPDF(doc.FileName, ms); yield return new(dataEntryDoc, DataEntryReGroupWindow.SplitIntoPages(doc.FileName, pdfDoc).ToList()); } } } public void DoExplodeAll() { var pages = ExplodeDocuments(); var groups = new List(); foreach(var (doc, docPages) in pages) { if(docPages.Count == 1) { groups.Add(new DocumentGroup(doc.Document.FileName, docPages, doc.Tag.ID)); } else { var extension = Path.GetExtension(doc.Document.FileName) ?? ""; var stem = Path.GetFileNameWithoutExtension(doc.Document.FileName); foreach(var (i, page) in docPages.WithIndex()) { var group = new DocumentGroup($"{stem} - {i + 1}{extension}", [page], doc.Tag.ID); groups.Add(group); } } } SavePageGroups(groups); DeleteItems(SelectedRows); Refresh(false,true); } public void DoExplode() { var tagID = GetSelectedTagID(); var pages = ExplodeDocuments(); var filename = ""; var allPages = new List(); foreach(var (doc, docPages) in pages) { filename = doc.Document.FileName; allPages.AddRange(docPages); } if (ShowDocumentWindow(allPages, filename, tagID)) { // ShowDocumentWindow already saves new scans, so we just need to get rid of the old ones. DeleteItems(SelectedRows); Refresh(false,true); } } public void DoRemove() { var updates = SelectedRows.Select(x => x.ToObject()).ToArray(); foreach (var update in updates) { update.Archived = DateTime.Now; DataEntryCache.Cache.Remove(update.Document.ID); } new Client().Save(updates,"Removed from Data Entry Panel"); Refresh(false,true); } public void DoChangeTags(Guid tagid) { var updates = SelectedRows.Select(x => x.ToObject()).ToArray(); foreach (var update in updates) { if (update.Tag.ID != tagid) { update.Tag.ID = tagid; update.EntityID = Guid.Empty; } } new Client().Save(updates.Where(x=>x.IsChanged()),"Updated Tags on Data Entry Panel"); Refresh(false,true); } public void DoChangeNote(string note) { var updates = SelectedRows.ToObjects().ToArray(); foreach (var update in updates) { if (!string.Equals(update.Note, note)) { update.Note = note; } } Client.Save(updates.Where(x => x.IsChanged()), "Updated Note on Data Entry Panel"); Refresh(false, true); } protected override DragDropEffects OnRowsDragStart(CoreRow[] rows) { var table = new CoreTable(); table.Columns.Add(new CoreColumn { ColumnName = "ID", DataType = typeof(Guid) }); foreach(var row in rows) { var newRow = table.NewRow(); newRow.Set(x => x.ID, row.Get(x => x.Document.ID)); table.Rows.Add(newRow); } return DragTable(typeof(Document), table); } public void UploadDocument(string filename, byte[] data, Guid tagID) { var document = new Document { FileName = filename, CRC = CoreUtils.CalculateCRC(data), TimeStamp = DateTime.Now, Data = data }; new Client().Save(document, ""); var dataentry = new DataEntryDocument { Document = { ID = document.ID }, Tag = { ID = tagID }, Employee = { ID = App.EmployeeID } }; if(Path.GetExtension(filename) == ".pdf") { dataentry.Thumbnail = ImageUtils.GetPDFThumbnail(data, 256, 256); } new Client().Save(dataentry, ""); DataEntryCache.Cache.Add(new DocumentCachedDocument(document)); Dispatcher.BeginInvoke(() => { Refresh(false, true); }); } private static PdfDocumentBase CombinePages(IEnumerable pages) { var document = new PdfDocument(); foreach (var page in pages) { document.ImportPage(page.Pdf, page.PageIndex); } return document; } private void SavePageGroups(IEnumerable groups) { Progress.ShowModal("Uploading Files", (progress) => { foreach (var group in groups) { progress.Report($"Uploading '{group.FileName}'"); var doc = CombinePages(group.Pages); byte[] data; using (var ms = new MemoryStream()) { doc.Save(ms); data = ms.ToArray(); } UploadDocument(group.FileName, data, group.TagID); } }); } public bool ShowDocumentWindow(List pages, string filename, Guid tagID) { var window = new DataEntryReGroupWindow(pages, filename, tagID); if (window.ShowDialog() == true) { SavePageGroups(window.Groups); return true; } return false; } public override DynamicGridColumns GenerateColumns() { var columns = new DynamicGridColumns(); columns.Add(x => x.Document.FileName, 0, "Filename", "", Alignment.MiddleLeft); columns.Add(x => x.Tag.Name, 100, "Tag", "", Alignment.MiddleLeft); return columns; } protected override void Reload( Filters criteria, Columns columns, ref SortOrder? sort, CancellationToken token, Action action) { criteria.Add(new Filter(x => x.Archived).IsEqualTo(DateTime.MinValue)); var tagFilter = new Filter(x => x.Tag.ID).InList(GetVisibleTags().Select(x => x.ID).ToArray()); if (Security.IsAllowed()) { tagFilter.Or(x => x.Tag.ID).IsEqualTo(Guid.Empty); } criteria.Add(tagFilter); base.Reload(criteria, columns, ref sort, token, action); } }