using Comal.Classes; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.WPF; using Syncfusion.Pdf; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows; using System.Windows.Media.Imaging; namespace PRSDesktop; public class DataEntryCachedDocument : ICachedDocument { public DateTime TimeStamp { get; set; } public Document? Document { get; set; } public Guid ID => Document?.ID ?? Guid.Empty; public DataEntryCachedDocument() { } public DataEntryCachedDocument(Document document) { Document = document; TimeStamp = document.TimeStamp; } public void DeserializeBinary(CoreBinaryReader reader, bool full) { TimeStamp = reader.ReadDateTime(); if (full) { Document = reader.ReadObject(); } } public void SerializeBinary(CoreBinaryWriter writer) { writer.Write(TimeStamp); if (Document is null) { throw new Exception("Cannot serialize incomplete CachedDocument"); } writer.WriteObject(Document); } } 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)) { } protected override DataEntryCachedDocument? LoadDocument(Guid id) { var document = Client.Query(new Filter(x => x.ID).IsEqualTo(id)) .ToObjects().FirstOrDefault(); if(document is not null) { return new DataEntryCachedDocument(document); } else { return null; } } /// /// Fetch a bunch of documents from the cache or the database, optionally checking against the timestamp listed in the database. /// /// /// /// If , then loads from the database for all cached documents, /// and if they are older, updates the cache. /// public IEnumerable LoadDocuments(IEnumerable ids, bool checkTimestamp = false) { var cached = new List(); var toLoad = new List(); foreach (var docID in ids) { if (Has(docID)) { cached.Add(docID); } else { toLoad.Add(docID); } } var loadedCached = new List(); if (cached.Count > 0) { var docs = Client.Query( new Filter(x => x.ID).InList(cached.ToArray()), new Columns(x => x.TimeStamp, x => x.ID)); foreach (var doc in docs.ToObjects()) { try { var timestamp = GetHeader(doc.ID).Document.TimeStamp; if (doc.TimeStamp > timestamp) { toLoad.Add(doc.ID); } else { loadedCached.Add(GetFull(doc.ID).Document.Document!); } } catch (Exception e) { CoreUtils.LogException("", e, "Error loading cached file"); toLoad.Add(doc.ID); } } } if (toLoad.Count > 0) { var loaded = Client.Query(new Filter(x => x.ID).InList(toLoad.ToArray())) .ToObjects().ToList(); foreach (var loadedDoc in loaded) { Add(new DataEntryCachedDocument(loadedDoc)); } return loaded.Concat(loadedCached); } else { return loadedCached; } } } 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.EntityID); HiddenColumns.Add(x => x.Archived); 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), new Columns(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(FluentList options) { base.DoReconfigure(options); options.BeginUpdate() .Clear() .Add(DynamicGridOption.MultiSelect) .Add(DynamicGridOption.DragSource) .Add(DynamicGridOption.DragTarget) .Add(DynamicGridOption.SelectColumns) .EndUpdate(); } 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), new Columns(x => x.ID)) .Rows.Any(); if (tagHasEmployee) { tagsList.Add(tag); } } } return tagsList; } private List GetVisibleTags() { _tags ??= GetVisibleTagList(); return _tags; } public void DoExplode() { Guid 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) { tagID = Guid.Empty; break; } } var docIDs = SelectedRows.Select(r => r.Get(c => c.Document.ID)).ToArray(); var docs = new Client() .Query( new Filter(x => x.ID).InList(docIDs), new Columns(x => x.ID).Add(x => x.Data).Add(x => x.FileName)) .ToObjects().ToDictionary(x => x.ID, x => x); var pages = new List(); string filename = ""; foreach (var docID in docIDs) { if (docs.TryGetValue(docID, out var doc)) { filename = doc.FileName; var ms = new MemoryStream(doc.Data); var pdfDoc = DataEntryReGroupWindow.RenderToPDF(doc.FileName, ms); foreach (var page in DataEntryReGroupWindow.SplitIntoPages(doc.FileName, pdfDoc)) { pages.Add(page); } } } if (ShowDocumentWindow(pages, 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); } 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 }, Thumbnail = ImageUtils.GetPDFThumbnail(data, 256, 256) }; new Client().Save(dataentry, ""); DataEntryCache.Cache.Add(new DataEntryCachedDocument(document)); Dispatcher.Invoke(() => { 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; } public bool ShowDocumentWindow(List pages, string filename, Guid tagID) { var window = new DataEntryReGroupWindow(pages, filename, tagID); if (window.ShowDialog() == true) { Progress.ShowModal("Uploading Files", (progress) => { foreach (var group in window.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); } }); return true; } return false; } protected override void GenerateColumns(DynamicGridColumns columns) { columns.Add(x => x.Document.FileName, 0, "Filename", "", Alignment.MiddleLeft); columns.Add(x => x.Tag.Name, 100, "Tag", "", Alignment.MiddleLeft); } protected override void Reload(Filters criteria, Columns columns, ref SortOrder? sort, 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, action); } }