DigitalFormDockGrid.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using InABox.Scripting;
  6. using InABox.Wpf.Reports;
  7. using InABox.WPF;
  8. using PRSDesktop.Configuration;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Linq;
  12. using System.Reflection;
  13. using System.Runtime.CompilerServices;
  14. using System.Threading.Tasks;
  15. using System.Windows;
  16. using System.Windows.Controls;
  17. using System.Windows.Media;
  18. using System.Windows.Media.Imaging;
  19. namespace PRSDesktop;
  20. public class DigitalFormDockGrid : DynamicGrid<DigitalFormDockModel>
  21. {
  22. private static readonly CoreFieldMap<IDigitalFormInstance, DigitalFormDockModel> _mappings =
  23. new CoreFieldMap<IDigitalFormInstance, DigitalFormDockModel>()
  24. .Add(x => x.ID, x => x.ID)
  25. .Add(x => x.Form.ID, x => x.FormID)
  26. .Add(x => x.Number, x => x.Number)
  27. .Add(x => x.Form.Description, x => x.FormName)
  28. .Add(x => x.FormCompleted, x => x.Completed)
  29. .Add(x => x.FormCompletedBy.UserID, x => x.CompletedBy)
  30. .Add(x => x.FormProcessed, x => x.Processed)
  31. .Add(x => x.Parent.ID, x => x.ParentID);
  32. public static readonly Dictionary<Type, System.Drawing.Bitmap> Images = new()
  33. {
  34. { typeof(AssignmentForm), PRSDesktop.Resources.assignments },
  35. { typeof(KanbanForm), PRSDesktop.Resources.kanban },
  36. { typeof(JobForm), PRSDesktop.Resources.project },
  37. { typeof(JobITPForm), PRSDesktop.Resources.checklist },
  38. { typeof(EmployeeForm), PRSDesktop.Resources.employees },
  39. { typeof(LeaveRequestForm), PRSDesktop.Resources.leave },
  40. { typeof(ManufacturingPacketStage), PRSDesktop.Resources.factory },
  41. { typeof(TimeSheetForm), PRSDesktop.Resources.time },
  42. { typeof(PurchaseOrderItemForm), PRSDesktop.Resources.purchase },
  43. { typeof(DeliveryForm), PRSDesktop.Resources.truck },
  44. };
  45. public List<Type> ExcludedTypes { get; private set; }
  46. public DateTime StartDate { get; set; }
  47. public DigitalFormDockGrid()
  48. {
  49. StartDate = DateTime.Today;
  50. ExcludedTypes = new List<Type>();
  51. ActionColumns.Add(new DynamicImageColumn(TypeImage)
  52. {
  53. Position = DynamicActionColumnPosition.Start,
  54. Filters = Images.Keys.Select(x => x.EntityName().Split('.').Last().SplitCamelCase()).ToArray(),
  55. FilterRecord = TypeFilter
  56. });
  57. ActionColumns.Add(new DynamicMenuColumn(MenuBuild, MenuStatus) { Position = DynamicActionColumnPosition.End });
  58. }
  59. protected override void Init()
  60. {
  61. }
  62. protected override void DoReconfigure(FluentList<DynamicGridOption> options)
  63. {
  64. options.BeginUpdate()
  65. .Clear()
  66. .Add(DynamicGridOption.FilterRows)
  67. .EndUpdate();
  68. }
  69. protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
  70. {
  71. var result = base.GetRowStyle(row, style);
  72. if (!row.Get<DigitalFormDockModel, DateTime>(x => x.Processed).IsEmpty())
  73. result = new DynamicGridRowStyle(result)
  74. {
  75. Background = new SolidColorBrush(Colors.LightGray),
  76. };
  77. return result;
  78. }
  79. private bool TypeFilter(CoreRow row, string[] filter)
  80. {
  81. string typename = row.Get<DigitalFormDockModel, Type>(x => x.FormType).EntityName().Split('.').Last().SplitCamelCase();
  82. return filter.Contains(typename);
  83. }
  84. private BitmapImage? TypeImage(CoreRow? arg)
  85. {
  86. if (arg is null)
  87. return null;
  88. var type = arg.Get<DigitalFormDockModel, Type>(x => x.FormType);
  89. return Images.GetValueOrDefault(type)?.AsBitmapImage();
  90. }
  91. private static readonly MethodInfo MenuBuildGenericMethod = typeof(DigitalFormDockGrid)
  92. .GetMethod(nameof(MenuBuildGeneric), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)!;
  93. private void MenuBuildGeneric<TEntityForm, TEntity, TEntityLink>(ContextMenu menu, DigitalFormDockModel formModel)
  94. where TEntityForm : Entity, IDigitalFormInstance<TEntityLink>, IRemotable, IPersistent, new()
  95. where TEntity : Entity, IRemotable, IPersistent, new()
  96. where TEntityLink : IEntityLink<TEntity>, new()
  97. {
  98. var filter = new Filter<TEntityForm>(x => x.ID).IsEqualTo(formModel.ID);
  99. var model = new DigitalFormReportDataModel<TEntityForm>(filter, formModel.FormID);
  100. if (Security.CanView<TEntity>())
  101. {
  102. menu.AddItem($"View {typeof(TEntity).Name}", Images.GetValueOrDefault(typeof(TEntityForm)), formModel, ViewEntity_Click<TEntity>);
  103. }
  104. var moduleTask = Task.Run(() =>
  105. {
  106. return new Client<CustomModule>().Query(
  107. new Filter<CustomModule>(x => x.DataModel).IsEqualTo(model.Name)
  108. .And(x => x.Section).IsEqualTo(formModel.FormID.ToString())
  109. .And(x => x.Visible).IsEqualTo(true)
  110. ).ToObjects<CustomModule>();
  111. });
  112. var modulesSeparator = menu.AddSeparatorIfNeeded();
  113. var modulesItem = menu.AddItem("Loading...", null, null, enabled: false);
  114. moduleTask.ContinueWith((task) =>
  115. {
  116. try
  117. {
  118. var index = menu.Items.IndexOf(modulesItem);
  119. menu.Items.Remove(modulesItem);
  120. var any = false;
  121. foreach (var module in task.Result)
  122. {
  123. any = true;
  124. menu.AddItem(
  125. module.Name,
  126. PRSDesktop.Resources.edit,
  127. () =>
  128. {
  129. try
  130. {
  131. if (ScriptDocument.RunCustomModule(model, new Dictionary<string, object[]>(), module.Script))
  132. {
  133. Refresh(false, true);
  134. }
  135. }
  136. catch (CompileException c)
  137. {
  138. MessageBox.Show(c.Message);
  139. }
  140. catch (Exception e)
  141. {
  142. MessageBox.Show(CoreUtils.FormatException(e));
  143. }
  144. },
  145. enabled: formModel.Processed.IsEmpty(),
  146. index: index);
  147. ++index;
  148. }
  149. if (!any && modulesSeparator is not null)
  150. {
  151. menu.Items.Remove(modulesSeparator);
  152. }
  153. }
  154. catch (Exception ex)
  155. {
  156. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in digital form dock while loading custom modules: {CoreUtils.FormatException(ex)}");
  157. MessageBox.Show($"Error: {ex.Message}", "Error");
  158. }
  159. }, TaskScheduler.FromCurrentSynchronizationContext());
  160. menu.AddSeparator();
  161. if (formModel.Processed.IsEmpty())
  162. {
  163. menu.AddItem(
  164. "Mark as Processed",
  165. PRSDesktop.Resources.lock_sml,
  166. () =>
  167. {
  168. var form = new TEntityForm
  169. {
  170. ID = formModel.ID,
  171. FormProcessed = formModel.Processed
  172. };
  173. form.CommitChanges();
  174. form.FormProcessed = DateTime.Now;
  175. using (new WaitCursor())
  176. {
  177. Client.Save(form, "Marked As Processed");
  178. Refresh(false, true);
  179. }
  180. });
  181. }
  182. else
  183. {
  184. menu.AddItem(
  185. "Clear Processed Flag",
  186. PRSDesktop.Resources.lock_sml,
  187. () =>
  188. {
  189. var form = new TEntityForm
  190. {
  191. ID = formModel.ID,
  192. FormProcessed = formModel.Processed
  193. };
  194. form.CommitChanges();
  195. form.FormProcessed = DateTime.MinValue;
  196. using (new WaitCursor())
  197. {
  198. Client.Save(form, "Processed Flag Cleared");
  199. Refresh(false, true);
  200. }
  201. });
  202. }
  203. if (typeof(TEntity).HasInterface<IJobScopedItem>())
  204. {
  205. menu.AddSeparatorIfNeeded();
  206. menu.AddItem("Set Job", PRSDesktop.Resources.project, formModel,
  207. SetJob_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate<Action<DigitalFormDockModel>>());
  208. menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel,
  209. SetJobScope_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate<Action<DigitalFormDockModel>>());
  210. }
  211. else if (typeof(TEntity) == typeof(Job) && typeof(TEntityForm) == typeof(JobForm))
  212. {
  213. menu.AddSeparatorIfNeeded();
  214. menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, SetJobScopeFromJob_Click);
  215. }
  216. if (Security.IsAllowed<CanCustomiseModules>())
  217. {
  218. menu.AddSeparatorIfNeeded();
  219. menu.AddItem("Customise Modules", PRSDesktop.Resources.script, () =>
  220. {
  221. var manager = new CustomModuleManager
  222. {
  223. Section = formModel.FormID.ToString(),
  224. DataModel = model
  225. };
  226. manager.ShowDialog();
  227. });
  228. }
  229. if (Security.IsAllowed<CanPrintReports>())
  230. {
  231. menu.AddSeparatorIfNeeded();
  232. var printItem = menu.AddItem("Print", PRSDesktop.Resources.printer, null);
  233. ReportUtils.PopulateMenu(printItem, formModel.FormID.ToString(), model, Security.IsAllowed<CanDesignReports>(), true);
  234. }
  235. }
  236. private static void ViewEntity_Click<TEntity>(DigitalFormDockModel model)
  237. where TEntity : Entity, IRemotable, IPersistent, new()
  238. {
  239. var entity = Client.Query(
  240. new Filter<TEntity>(x => x.ID).IsEqualTo(model.ParentID),
  241. DynamicGridUtils.LoadEditorColumns<TEntity>(new Columns<TEntity>(x => x.ID)))
  242. .ToObjects<TEntity>().First();
  243. var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicGrid<>), typeof(TEntity)) as DynamicGrid<TEntity>)!;
  244. grid.EditItems(new TEntity[] { entity });
  245. }
  246. private static void SetJobScopeFromJob_Click(DigitalFormDockModel formModel)
  247. {
  248. var instance = Client.Query(
  249. new Filter<JobForm>(x => x.ID).IsEqualTo(formModel.ID),
  250. LookupFactory.DefineFilterColumns<JobForm, JobScope>()
  251. .Add(x => x.ID)
  252. .Add(x => x.Parent.ID))
  253. .ToObjects<JobForm>().First();
  254. var job = Client.Query(
  255. new Filter<Job>(x => x.ID).IsEqualTo(instance.Parent.ID),
  256. LookupFactory.DefineFilterColumns<Job, JobScope>()).ToObjects<Job>().First();
  257. var window = new MultiSelectDialog<JobScope>(
  258. new Filters<JobScope>()
  259. .Add(LookupFactory.DefineFilter<Job, JobScope>(new Job[] { job }))
  260. .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(instance.Parent.ID))
  261. .Combine(),
  262. new Columns<JobScope>(x => x.ID).Add(x => x.Number), multiselect: false);
  263. if (!window.ShowDialog())
  264. {
  265. return;
  266. }
  267. var scope = window.Data().ToObjects<JobScope>().First();
  268. instance.JobScope.ID = scope.ID;
  269. Client.Save(instance, "Linked scope set by user from Digital forms dock.");
  270. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  271. }
  272. private static readonly MethodInfo SetJobScope_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJobScope_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
  273. private static void SetJobScope_Click<TEntity>(DigitalFormDockModel formModel)
  274. where TEntity : Entity, IJobScopedItem, IRemotable, IPersistent, new()
  275. {
  276. var entity = Client.Query(
  277. new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
  278. LookupFactory.DefineFilterColumns<TEntity, JobScope>()
  279. .Add(x => x.ID)
  280. .Add(x => x.JobLink.ID)
  281. .Add(x => x.JobScope.ID)
  282. .Add(x => x.JobScope.Number))
  283. .ToObjects<TEntity>().First();
  284. if (entity.JobLink.ID == Guid.Empty)
  285. {
  286. MessageBox.Show($"{typeof(TEntity).Name} is not linked to a job. Please select a job first.");
  287. return;
  288. }
  289. var window = new MultiSelectDialog<JobScope>(
  290. new Filters<JobScope>()
  291. .Add(LookupFactory.DefineFilter<TEntity, JobScope>(new TEntity[] { entity }))
  292. .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(entity.JobLink.ID))
  293. .Combine(),
  294. new Columns<JobScope>(x => x.ID).Add(x => x.Number),
  295. multiselect: false);
  296. if (!window.ShowDialog(nameof(JobScope.Number), entity.JobScope.Number, Syncfusion.Data.FilterType.Equals))
  297. {
  298. return;
  299. }
  300. var scope = window.Data().ToObjects<JobScope>().First();
  301. entity.JobScope.ID = scope.ID;
  302. Client.Save(entity, "Linked scope set by user from Digital forms dock.");
  303. MessageBox.Show($"{typeof(TEntity).Name} has been assigned to scope {scope.Number}.");
  304. }
  305. private static readonly MethodInfo SetJob_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJob_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
  306. private static void SetJob_Click<TEntity>(DigitalFormDockModel formModel)
  307. where TEntity: Entity, IJobScopedItem, IRemotable, IPersistent, new()
  308. {
  309. var entity = Client.Query(
  310. new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
  311. LookupFactory.DefineFilterColumns<TEntity, Job>()
  312. .Add(x => x.ID)
  313. .Add(x => x.JobLink.ID)
  314. .Add(x => x.JobLink.JobNumber))
  315. .ToObjects<TEntity>().First();
  316. var window = new MultiSelectDialog<Job>(
  317. LookupFactory.DefineFilter<TEntity, Job>(new TEntity[] { entity }),
  318. new Columns<Job>(x => x.DefaultScope.ID).Add(x => x.JobNumber),
  319. multiselect: false);
  320. if (!window.ShowDialog(nameof(Job.JobNumber), entity.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals))
  321. {
  322. return;
  323. }
  324. var job = window.Data().ToObjects<Job>().First();
  325. entity.JobLink.ID = job.ID;
  326. entity.JobLink.Synchronise(job);
  327. Client.Save(entity, "Linked job set by user from Digital forms dock.");
  328. MessageBox.Show($"{typeof(TEntity).Name} has been assigned to job {job.JobNumber}.");
  329. }
  330. private void MenuBuild(DynamicMenuColumn column, CoreRow? row)
  331. {
  332. if (row is null) return;
  333. var form = row.ToObject<DigitalFormDockModel>();
  334. var linkType = DFUtils.FormEntityLinkType(form.FormType);
  335. var entityType = DFUtils.FormEntityType(form.FormType);
  336. MenuBuildGenericMethod.MakeGenericMethod(form.FormType, entityType, linkType).Invoke(this, new object?[] { column.GetMenu(), form });
  337. }
  338. private DynamicMenuStatus MenuStatus(CoreRow row)
  339. {
  340. if (row == null) return DynamicMenuStatus.Hidden;
  341. return DynamicMenuStatus.Enabled;
  342. }
  343. protected override void Reload(Filters<DigitalFormDockModel> criteria, Columns<DigitalFormDockModel> columns, ref SortOrder<DigitalFormDockModel>? sort, Action<CoreTable, Exception?> action)
  344. {
  345. var queryDefs = new Dictionary<string, IQueryDef>();
  346. var types = DFUtils.GetFormInstanceTypes().Except(ExcludedTypes).ToList();
  347. foreach (var type in types)
  348. {
  349. var filter = Filter.Create<IDigitalFormInstance>(type, x => x.FormCompleted).IsGreaterThanOrEqualTo(StartDate)
  350. .And<IDigitalFormInstance>(x => x.FormCancelled).IsEqualTo(DateTime.MinValue);
  351. var cols = Columns.Create<IDigitalFormInstance>(type)
  352. .Add<IDigitalFormInstance>(c => c.ID)
  353. .Add<IDigitalFormInstance>(c => c.Parent.ID)
  354. .Add<IDigitalFormInstance>(c => c.Form.ID)
  355. .Add<IDigitalFormInstance>(c => c.Number)
  356. .Add<IDigitalFormInstance>(c => c.Form.Description)
  357. .Add<IDigitalFormInstance>(c => c.FormCompleted)
  358. .Add<IDigitalFormInstance>(c => c.FormCompletedBy.UserID)
  359. .Add<IDigitalFormInstance>(c => c.FormProcessed);
  360. var sorts = SortOrder.Create<IDigitalFormInstance>(type, x => x.FormCompleted, SortDirection.Descending);
  361. queryDefs.Add(
  362. type.ToString(),
  363. new QueryDef(type)
  364. {
  365. Filter = filter,
  366. Columns = cols,
  367. SortOrder = sorts
  368. });
  369. }
  370. Client.QueryMultiple(
  371. (results, e) =>
  372. {
  373. if(results is not null)
  374. {
  375. var data = new CoreTable();
  376. data.LoadColumns(typeof(DigitalFormDockModel));
  377. foreach (var type in types)
  378. data.LoadFrom(
  379. results[type.ToString()],
  380. _mappings,
  381. (r) => r.Set<DigitalFormDockModel, Type>(x => x.FormType, type)
  382. );
  383. action.Invoke(data, null);
  384. }
  385. else if(e is not null)
  386. {
  387. Logger.Send(LogType.Error, ClientFactory.UserID, CoreUtils.FormatException(e));
  388. MessageBox.Show($"Error: {e.Message}");
  389. }
  390. },
  391. queryDefs);
  392. }
  393. protected override DigitalFormDockModel LoadItem(CoreRow row)
  394. {
  395. return row.ToObject<DigitalFormDockModel>();
  396. }
  397. public override void SaveItem(DigitalFormDockModel item)
  398. {
  399. }
  400. protected override void DeleteItems(params CoreRow[] rows)
  401. {
  402. }
  403. protected override void DoDoubleClick(object sender)
  404. {
  405. base.DoDoubleClick(sender);
  406. var row = SelectedRows.FirstOrDefault();
  407. if (row is null)
  408. return;
  409. var instanceID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
  410. var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
  411. var formType = row.Get<DigitalFormDockModel, Type>(x => x.FormType);
  412. var formInstance = Client.Create(formType)
  413. .Query(
  414. Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(instanceID),
  415. DynamicFormEditWindow.FormColumns(formType))
  416. .Rows.FirstOrDefault()
  417. ?.ToObject(formType) as IDigitalFormInstance;
  418. if (formInstance is null)
  419. {
  420. return;
  421. }
  422. if (formID == Guid.Empty)
  423. {
  424. var window = new DeletedFormWindow();
  425. window.FormData = formInstance?.FormData ?? "";
  426. window.ShowDialog();
  427. return;
  428. }
  429. if (DynamicFormEditWindow.EditDigitalForm(formInstance, out var model))
  430. {
  431. model.Update(null);
  432. }
  433. }
  434. }