DataEntryGrid.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Configuration;
  4. using InABox.Core;
  5. using InABox.DynamicGrid;
  6. using InABox.WPF;
  7. using PRSDesktop.Panels.DataEntry;
  8. using Syncfusion.Pdf;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Threading;
  14. using System.Windows;
  15. using System.Windows.Media.Imaging;
  16. namespace PRSDesktop;
  17. public class DataEntryCache : DocumentCache
  18. {
  19. public override TimeSpan MaxAge => TimeSpan.FromDays(new UserConfiguration<DataEntryPanelSettings>().Load().CacheAge);
  20. private static DataEntryCache? _cache;
  21. public static DataEntryCache Cache
  22. {
  23. get
  24. {
  25. _cache ??= DocumentCaches.GetOrRegister<DataEntryCache>();
  26. return _cache;
  27. }
  28. }
  29. public DataEntryCache(): base(nameof(DataEntryCache)) { }
  30. }
  31. public class DataEntryGrid : DynamicDataGrid<DataEntryDocument>
  32. {
  33. private List<DataEntryTag>? _tags;
  34. public DataEntryGrid()
  35. {
  36. HiddenColumns.Add(x => x.Tag.ID);
  37. HiddenColumns.Add(x => x.Tag.AppliesTo);
  38. HiddenColumns.Add(x => x.Document.ID);
  39. HiddenColumns.Add(x => x.Document.FileName);
  40. HiddenColumns.Add(x => x.EntityID);
  41. HiddenColumns.Add(x => x.Archived);
  42. HiddenColumns.Add(x => x.Note);
  43. ActionColumns.Add(new DynamicImageColumn(LinkedImage) { Position = DynamicActionColumnPosition.Start });
  44. var tagFilter = new Filter<DataEntryDocument>(x => x.Tag.ID).InList(GetVisibleTags().Select(x => x.ID).ToArray());
  45. if (Security.IsAllowed<CanSetupDataEntryTags>())
  46. {
  47. tagFilter.Or(x => x.Tag.ID).IsEqualTo(Guid.Empty);
  48. }
  49. var docs = Client.Query(
  50. new Filter<DataEntryDocument>(x => x.Archived).IsEqualTo(DateTime.MinValue)
  51. .And(tagFilter),
  52. Columns.None<DataEntryDocument>().Add(x => x.Document.ID));
  53. DataEntryCache.Cache.ClearOld();
  54. DataEntryCache.Cache.EnsureStrict(docs.Rows.Select(x => x.Get<DataEntryDocument, Guid>(x => x.Document.ID)).ToArray());
  55. }
  56. private static readonly BitmapImage link = PRSDesktop.Resources.link.AsBitmapImage();
  57. private BitmapImage? LinkedImage(CoreRow? arg)
  58. {
  59. return arg == null
  60. ? link
  61. : arg.Get<DataEntryDocument, Guid>(x => x.EntityID) != Guid.Empty
  62. ? link
  63. : null;
  64. }
  65. protected override void DoReconfigure(DynamicGridOptions options)
  66. {
  67. base.DoReconfigure(options);
  68. options.Clear();
  69. options.FilterRows = true;
  70. options.MultiSelect = true;
  71. options.DragSource = true;
  72. options.DragTarget = true;
  73. options.SelectColumns = true;
  74. }
  75. public static List<DataEntryTag> GetVisibleTagList()
  76. {
  77. var tags = new Client<DataEntryTag>().Query().ToObjects<DataEntryTag>().ToList();
  78. var tagsList = new List<DataEntryTag>();
  79. foreach (var tag in tags)
  80. {
  81. var entity = CoreUtils.GetEntityOrNull(tag.AppliesTo);
  82. if (entity is null || Security.CanView(entity))
  83. {
  84. var tagHasEmployee = new Client<DataEntryTagDistributionEmployee>()
  85. .Query(
  86. new Filter<DataEntryTagDistributionEmployee>(x => x.Tag.ID).IsEqualTo(tag.ID)
  87. .And(x => x.Employee.ID).IsEqualTo(App.EmployeeID),
  88. Columns.None<DataEntryTagDistributionEmployee>().Add(x => x.ID))
  89. .Rows.Any();
  90. if (tagHasEmployee)
  91. {
  92. tagsList.Add(tag);
  93. }
  94. }
  95. }
  96. return tagsList;
  97. }
  98. private List<DataEntryTag> GetVisibleTags()
  99. {
  100. _tags ??= GetVisibleTagList();
  101. return _tags;
  102. }
  103. /// <summary>
  104. /// Gets the currently selected tag ID, if all selected rows belong to the same tag.
  105. /// </summary>
  106. /// <returns></returns>
  107. private Guid GetSelectedTagID()
  108. {
  109. var tagID = Guid.Empty;
  110. foreach (var row in SelectedRows)
  111. {
  112. var rowTag = row.Get<DataEntryDocument, Guid>(x => x.Tag.ID);
  113. if (tagID == Guid.Empty)
  114. {
  115. tagID = rowTag;
  116. }
  117. else if (rowTag != tagID)
  118. {
  119. return Guid.Empty;
  120. }
  121. }
  122. return tagID;
  123. }
  124. private IEnumerable<Tuple<DataEntryDocument, List<DataEntryReGroupWindow.Page>>> ExplodeDocuments()
  125. {
  126. var dataEntryDocs = SelectedRows.ToArray<DataEntryDocument>();
  127. var docIDs = dataEntryDocs.Select(r => r.Document.ID).ToArray();
  128. var docs = new Client<Document>()
  129. .Query(
  130. new Filter<Document>(x => x.ID).InList(docIDs),
  131. Columns.None<Document>().Add(x => x.ID).Add(x => x.Data).Add(x => x.FileName))
  132. .ToObjects<Document>().ToDictionary(x => x.ID, x => x);
  133. foreach (var dataEntryDoc in dataEntryDocs)
  134. {
  135. if (docs.TryGetValue(dataEntryDoc.Document.ID, out var doc))
  136. {
  137. var ms = new MemoryStream(doc.Data);
  138. var pdfDoc = DataEntryReGroupWindow.RenderToPDF(doc.FileName, ms);
  139. yield return new(dataEntryDoc, DataEntryReGroupWindow.SplitIntoPages(doc.FileName, pdfDoc).ToList());
  140. }
  141. }
  142. }
  143. public void DoExplodeAll()
  144. {
  145. var pages = ExplodeDocuments();
  146. var groups = new List<DocumentGroup>();
  147. foreach(var (doc, docPages) in pages)
  148. {
  149. if(docPages.Count == 1)
  150. {
  151. groups.Add(new DocumentGroup(doc.Document.FileName, docPages, doc.Tag.ID));
  152. }
  153. else
  154. {
  155. var extension = Path.GetExtension(doc.Document.FileName) ?? "";
  156. var stem = Path.GetFileNameWithoutExtension(doc.Document.FileName);
  157. foreach(var (i, page) in docPages.WithIndex())
  158. {
  159. var group = new DocumentGroup($"{stem} - {i + 1}{extension}", [page], doc.Tag.ID);
  160. groups.Add(group);
  161. }
  162. }
  163. }
  164. SavePageGroups(groups);
  165. DeleteItems(SelectedRows);
  166. Refresh(false,true);
  167. }
  168. public void DoExplode()
  169. {
  170. var tagID = GetSelectedTagID();
  171. var pages = ExplodeDocuments();
  172. var filename = "";
  173. var allPages = new List<DataEntryReGroupWindow.Page>();
  174. foreach(var (doc, docPages) in pages)
  175. {
  176. filename = doc.Document.FileName;
  177. allPages.AddRange(docPages);
  178. }
  179. if (ShowDocumentWindow(allPages, filename, tagID))
  180. {
  181. // ShowDocumentWindow already saves new scans, so we just need to get rid of the old ones.
  182. DeleteItems(SelectedRows);
  183. Refresh(false,true);
  184. }
  185. }
  186. public void DoRemove()
  187. {
  188. var updates = SelectedRows.Select(x => x.ToObject<DataEntryDocument>()).ToArray();
  189. foreach (var update in updates)
  190. {
  191. update.Archived = DateTime.Now;
  192. DataEntryCache.Cache.Remove(update.Document.ID);
  193. }
  194. new Client<DataEntryDocument>().Save(updates,"Removed from Data Entry Panel");
  195. Refresh(false,true);
  196. }
  197. public void DoChangeTags(Guid tagid)
  198. {
  199. var updates = SelectedRows.Select(x => x.ToObject<DataEntryDocument>()).ToArray();
  200. foreach (var update in updates)
  201. {
  202. if (update.Tag.ID != tagid)
  203. {
  204. update.Tag.ID = tagid;
  205. update.EntityID = Guid.Empty;
  206. }
  207. }
  208. new Client<DataEntryDocument>().Save(updates.Where(x=>x.IsChanged()),"Updated Tags on Data Entry Panel");
  209. Refresh(false,true);
  210. }
  211. public void DoChangeNote(string note)
  212. {
  213. var updates = SelectedRows.ToObjects<DataEntryDocument>().ToArray();
  214. foreach (var update in updates)
  215. {
  216. if (!string.Equals(update.Note, note))
  217. {
  218. update.Note = note;
  219. }
  220. }
  221. Client.Save(updates.Where(x => x.IsChanged()), "Updated Note on Data Entry Panel");
  222. Refresh(false, true);
  223. }
  224. protected override DragDropEffects OnRowsDragStart(CoreRow[] rows)
  225. {
  226. var table = new CoreTable();
  227. table.Columns.Add(new CoreColumn { ColumnName = "ID", DataType = typeof(Guid) });
  228. foreach(var row in rows)
  229. {
  230. var newRow = table.NewRow();
  231. newRow.Set<Document, Guid>(x => x.ID, row.Get<DataEntryDocument, Guid>(x => x.Document.ID));
  232. table.Rows.Add(newRow);
  233. }
  234. return DragTable(typeof(Document), table);
  235. }
  236. public void UploadDocument(string filename, byte[] data, Guid tagID)
  237. {
  238. var document = new Document
  239. {
  240. FileName = filename,
  241. CRC = CoreUtils.CalculateCRC(data),
  242. TimeStamp = DateTime.Now,
  243. Data = data
  244. };
  245. new Client<Document>().Save(document, "");
  246. var dataentry = new DataEntryDocument
  247. {
  248. Document =
  249. {
  250. ID = document.ID
  251. },
  252. Tag =
  253. {
  254. ID = tagID
  255. },
  256. Employee =
  257. {
  258. ID = App.EmployeeID
  259. }
  260. };
  261. if(Path.GetExtension(filename) == ".pdf")
  262. {
  263. dataentry.Thumbnail = ImageUtils.GetPDFThumbnail(data, 256, 256);
  264. }
  265. new Client<DataEntryDocument>().Save(dataentry, "");
  266. DataEntryCache.Cache.Add(new DocumentCachedDocument(document));
  267. Dispatcher.BeginInvoke(() =>
  268. {
  269. Refresh(false, true);
  270. });
  271. }
  272. private static PdfDocumentBase CombinePages(IEnumerable<DataEntryReGroupWindow.Page> pages)
  273. {
  274. var document = new PdfDocument();
  275. foreach (var page in pages)
  276. {
  277. document.ImportPage(page.Pdf, page.PageIndex);
  278. }
  279. return document;
  280. }
  281. private void SavePageGroups(IEnumerable<DocumentGroup> groups)
  282. {
  283. Progress.ShowModal("Uploading Files", (progress) =>
  284. {
  285. foreach (var group in groups)
  286. {
  287. progress.Report($"Uploading '{group.FileName}'");
  288. var doc = CombinePages(group.Pages);
  289. byte[] data;
  290. using (var ms = new MemoryStream())
  291. {
  292. doc.Save(ms);
  293. data = ms.ToArray();
  294. }
  295. UploadDocument(group.FileName, data, group.TagID);
  296. }
  297. });
  298. }
  299. public bool ShowDocumentWindow(List<DataEntryReGroupWindow.Page> pages, string filename, Guid tagID)
  300. {
  301. var window = new DataEntryReGroupWindow(pages, filename, tagID);
  302. if (window.ShowDialog() == true)
  303. {
  304. SavePageGroups(window.Groups);
  305. return true;
  306. }
  307. return false;
  308. }
  309. public override DynamicGridColumns GenerateColumns()
  310. {
  311. var columns = new DynamicGridColumns();
  312. columns.Add<DataEntryDocument, string>(x => x.Document.FileName, 0, "Filename", "", Alignment.MiddleLeft);
  313. columns.Add<DataEntryDocument, string>(x => x.Tag.Name, 100, "Tag", "", Alignment.MiddleLeft);
  314. return columns;
  315. }
  316. protected override void Reload(
  317. Filters<DataEntryDocument> criteria, Columns<DataEntryDocument> columns, ref SortOrder<DataEntryDocument>? sort,
  318. CancellationToken token, Action<CoreTable?, Exception?> action)
  319. {
  320. criteria.Add(new Filter<DataEntryDocument>(x => x.Archived).IsEqualTo(DateTime.MinValue));
  321. var tagFilter = new Filter<DataEntryDocument>(x => x.Tag.ID).InList(GetVisibleTags().Select(x => x.ID).ToArray());
  322. if (Security.IsAllowed<CanSetupDataEntryTags>())
  323. {
  324. tagFilter.Or(x => x.Tag.ID).IsEqualTo(Guid.Empty);
  325. }
  326. criteria.Add(tagFilter);
  327. base.Reload(criteria, columns, ref sort, token, action);
  328. }
  329. }