DigitalFormDockGrid.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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.Threading;
  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.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. GetFilter = () => new StaticColumnFilter<Type>(
  55. FormType,
  56. Images.Keys.Select(x => new Tuple<string, Type>(x.EntityName().Split('.').Last().SplitCamelCase(), x)).ToArray())
  57. });
  58. ActionColumns.Add(new DynamicMenuColumn(MenuBuild, MenuStatus) { Position = DynamicActionColumnPosition.End });
  59. }
  60. protected override void DoReconfigure(DynamicGridOptions options)
  61. {
  62. options.Clear();
  63. options.FilterRows = true;
  64. }
  65. protected override DynamicGridStyle GetRowStyle(CoreRow row, DynamicGridStyle style)
  66. {
  67. var result = base.GetRowStyle(row, style);
  68. if (!row.Get<DigitalFormDockModel, DateTime>(x => x.Processed).IsEmpty())
  69. result = new DynamicGridRowStyle(result)
  70. {
  71. Background = new SolidColorBrush(Colors.LightGray),
  72. };
  73. return result;
  74. }
  75. private Type FormType(CoreRow row)
  76. {
  77. return row.Get<DigitalFormDockModel, Type>(x => x.FormType);
  78. }
  79. private BitmapImage? TypeImage(CoreRow? arg)
  80. {
  81. if (arg is null)
  82. return null;
  83. var type = arg.Get<DigitalFormDockModel, Type>(x => x.FormType);
  84. return Images.GetValueOrDefault(type)?.AsBitmapImage();
  85. }
  86. private static readonly MethodInfo MenuBuildGenericMethod = typeof(DigitalFormDockGrid)
  87. .GetMethod(nameof(MenuBuildGeneric), BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)!;
  88. private void MenuBuildGeneric<TEntityForm, TEntity, TEntityLink>(ContextMenu menu, DigitalFormDockModel formModel)
  89. where TEntityForm : Entity, IDigitalFormInstance<TEntityLink>, IRemotable, IPersistent, new()
  90. where TEntity : Entity, IRemotable, IPersistent, new()
  91. where TEntityLink : IEntityLink<TEntity>, new()
  92. {
  93. var filter = new Filter<TEntityForm>(x => x.ID).IsEqualTo(formModel.ID);
  94. var model = new DigitalFormReportDataModel<TEntityForm>(filter, formModel.FormID);
  95. if (Security.CanView<TEntity>())
  96. {
  97. menu.AddItem($"View {typeof(TEntity).Name}", Images.GetValueOrDefault(typeof(TEntityForm)), formModel, ViewEntity_Click<TEntity>);
  98. }
  99. var moduleTask = Task.Run(() =>
  100. {
  101. return new Client<CustomModule>().Query(
  102. new Filter<CustomModule>(x => x.DataModel).IsEqualTo(model.Name)
  103. .And(x => x.Section).IsEqualTo(formModel.FormID.ToString())
  104. .And(x => x.Visible).IsEqualTo(true)
  105. ).ToObjects<CustomModule>();
  106. });
  107. var modulesSeparator = menu.AddSeparatorIfNeeded();
  108. var modulesItem = menu.AddItem("Loading...", null, null, enabled: false);
  109. moduleTask.ContinueWith((task) =>
  110. {
  111. try
  112. {
  113. var index = menu.Items.IndexOf(modulesItem);
  114. menu.Items.Remove(modulesItem);
  115. var any = false;
  116. foreach (var module in task.Result)
  117. {
  118. any = true;
  119. menu.AddItem(
  120. module.Name,
  121. PRSDesktop.Resources.edit,
  122. () =>
  123. {
  124. try
  125. {
  126. if (ScriptDocument.RunCustomModule(model, new Dictionary<string, object[]>(), module.Script))
  127. {
  128. Refresh(false, true);
  129. }
  130. }
  131. catch (CompileException c)
  132. {
  133. MessageBox.Show(c.Message);
  134. }
  135. catch (Exception e)
  136. {
  137. MessageBox.Show(CoreUtils.FormatException(e));
  138. }
  139. },
  140. enabled: formModel.Processed.IsEmpty(),
  141. index: index);
  142. ++index;
  143. }
  144. if (!any && modulesSeparator is not null)
  145. {
  146. menu.Items.Remove(modulesSeparator);
  147. }
  148. }
  149. catch (Exception ex)
  150. {
  151. Logger.Send(LogType.Error, ClientFactory.UserID, $"Error in digital form dock while loading custom modules: {CoreUtils.FormatException(ex)}");
  152. MessageBox.Show($"Error: {ex.Message}", "Error");
  153. }
  154. }, TaskScheduler.FromCurrentSynchronizationContext());
  155. menu.AddSeparator();
  156. if (formModel.Processed.IsEmpty())
  157. {
  158. menu.AddItem(
  159. "Mark as Processed",
  160. PRSDesktop.Resources.lock_sml,
  161. () =>
  162. {
  163. var form = new TEntityForm
  164. {
  165. FormProcessed = formModel.Processed
  166. }.SetID(formModel.ID);
  167. form.CommitChanges();
  168. form.FormProcessed = DateTime.Now;
  169. using (new WaitCursor())
  170. {
  171. Client.Save(form, "Marked As Processed");
  172. Refresh(false, true);
  173. }
  174. });
  175. }
  176. else
  177. {
  178. menu.AddItem(
  179. "Clear Processed Flag",
  180. PRSDesktop.Resources.lock_sml,
  181. () =>
  182. {
  183. var form = new TEntityForm
  184. {
  185. FormProcessed = formModel.Processed
  186. }.SetID(formModel.ID);
  187. form.CommitChanges();
  188. form.FormProcessed = DateTime.MinValue;
  189. using (new WaitCursor())
  190. {
  191. Client.Save(form, "Processed Flag Cleared");
  192. Refresh(false, true);
  193. }
  194. });
  195. }
  196. if (typeof(TEntity).HasInterface<IJobScopedItem>())
  197. {
  198. menu.AddSeparatorIfNeeded();
  199. menu.AddItem("Set Job", PRSDesktop.Resources.project, formModel,
  200. SetJob_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate<Action<DigitalFormDockModel>>());
  201. menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel,
  202. SetJobScope_ClickMethod.MakeGenericMethod(typeof(TEntity)).CreateDelegate<Action<DigitalFormDockModel>>());
  203. }
  204. else if (typeof(TEntity) == typeof(Job) && typeof(TEntityForm) == typeof(JobForm))
  205. {
  206. menu.AddSeparatorIfNeeded();
  207. var scopeheader = menu.AddItem("Set Job Scope", PRSDesktop.Resources.project, formModel, dockModel => { });
  208. var scopes = Client.Query<JobScope>(
  209. new Filter<JobScope>(x => x.Job.ID).IsEqualTo(formModel.ParentID),
  210. Columns.None<JobScope>()
  211. .Add(x => x.ID)
  212. .Add(x => x.Number)
  213. .Add(x => x.Description)
  214. ).ToObjects<JobScope>();
  215. foreach (var scope in scopes)
  216. {
  217. var existingscope = new MenuItem() { Header = $"{scope.Number}: {scope.Description}", DataContext = formModel, Tag = scope };
  218. existingscope.Click += SetJobScopeFromMenu_Click;
  219. scopeheader.Items.Add(existingscope);
  220. }
  221. if (scopes.Any())
  222. scopeheader.Items.Add(new Separator());
  223. var newscope = new MenuItem() { Header = $"Create New Scope", DataContext = formModel };
  224. newscope.Click += CreateJobScopeFromMenu_Click;
  225. scopeheader.Items.Add(newscope);
  226. }
  227. if (Security.IsAllowed<CanCustomiseModules>())
  228. {
  229. menu.AddSeparatorIfNeeded();
  230. menu.AddItem("Customise Modules", PRSDesktop.Resources.script, () =>
  231. {
  232. var manager = new CustomModuleManager
  233. {
  234. Section = formModel.FormID.ToString(),
  235. DataModel = model
  236. };
  237. manager.ShowDialog();
  238. });
  239. }
  240. if (Security.IsAllowed<CanPrintReports>())
  241. {
  242. menu.AddSeparatorIfNeeded();
  243. var printItem = menu.AddItem("Print", PRSDesktop.Resources.printer, null);
  244. ReportUtils.PopulateMenu(printItem, formModel.FormID.ToString(), model, Security.IsAllowed<CanDesignReports>(), true);
  245. }
  246. }
  247. private static void UpdateScopeID(DigitalFormDockModel formModel, Guid scopeid)
  248. {
  249. var instance = Client.Query(
  250. new Filter<JobForm>(x => x.ID).IsEqualTo(formModel.ID),
  251. Columns.Required<JobForm>()
  252. ).ToObjects<JobForm>().First();
  253. instance.JobScope.ID = scopeid;
  254. Client.Save(instance, "Linked scope set by user from Digital forms dock.");
  255. }
  256. private void CreateJobScopeFromMenu_Click(object sender, RoutedEventArgs e)
  257. {
  258. if ((sender as MenuItem)?.DataContext is DigitalFormDockModel model)
  259. {
  260. var scope = new JobScope();
  261. scope.Job.ID = model.ParentID;
  262. scope.Description = model.FormName;
  263. scope.Type = JobScopeType.Variation;
  264. if (new DynamicDataGrid<JobScope>().EditItems(new[] { scope }))
  265. {
  266. UpdateScopeID(model, scope.ID);
  267. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  268. // Update the form scope here (from the SetScope) stuff
  269. }
  270. }
  271. }
  272. private void SetJobScopeFromMenu_Click(object sender, RoutedEventArgs e)
  273. {
  274. if (sender is MenuItem mi && mi.DataContext is DigitalFormDockModel model && mi.Tag is JobScope scope)
  275. {
  276. UpdateScopeID(model, scope.ID);
  277. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  278. // Update the form scope here (from the SetScope) stuff
  279. }
  280. }
  281. private static void ViewEntity_Click<TEntity>(DigitalFormDockModel model)
  282. where TEntity : Entity, IRemotable, IPersistent, new()
  283. {
  284. var entity = Client.Query(
  285. new Filter<TEntity>(x => x.ID).IsEqualTo(model.ParentID),
  286. DynamicGridUtils.LoadEditorColumns<TEntity>(Columns.None<TEntity>()))
  287. .ToObjects<TEntity>().First();
  288. var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicGrid<>), typeof(TEntity)) as DynamicGrid<TEntity>)!;
  289. grid.EditItems(new TEntity[] { entity });
  290. }
  291. private static void SetJobScopeFromJob_Click(DigitalFormDockModel formModel)
  292. {
  293. var instance = Client.Query(
  294. new Filter<JobForm>(x => x.ID).IsEqualTo(formModel.ID),
  295. LookupFactory.DefineLookupFilterColumns<JobForm, JobScope, JobScopeLink>(x => x.JobScope)
  296. .Add(x => x.ID)
  297. .Add(x => x.Parent.ID))
  298. .ToObjects<JobForm>().First();
  299. var job = Client.Query(
  300. new Filter<Job>(x => x.ID).IsEqualTo(instance.Parent.ID),
  301. LookupFactory.DefineChildFilterColumns<Job, JobScope>()).ToObjects<Job>().First();
  302. var window = new MultiSelectDialog<JobScope>(
  303. new Filters<JobScope>()
  304. .Add(LookupFactory.DefineChildFilter<Job, JobScope>(new Job[] { job }))
  305. .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(instance.Parent.ID))
  306. .Combine(),
  307. Columns.None<JobScope>().Add(x => x.ID).Add(x => x.Number), multiselect: false);
  308. if (!window.ShowDialog())
  309. {
  310. return;
  311. }
  312. var scope = window.Data().ToObjects<JobScope>().First();
  313. instance.JobScope.ID = scope.ID;
  314. Client.Save(instance, "Linked scope set by user from Digital forms dock.");
  315. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  316. }
  317. private static readonly MethodInfo SetJobScope_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJobScope_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
  318. private static void SetJobScope_Click<TEntity>(DigitalFormDockModel formModel)
  319. where TEntity : Entity, IJobScopedItem, IRemotable, IPersistent, new()
  320. {
  321. var entity = Client.Query(
  322. new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
  323. LookupFactory.DefineLookupFilterColumns<TEntity, JobScope, JobScopeLink>(x => x.JobScope)
  324. .Add(x => x.ID)
  325. .Add(x => x.JobLink.ID)
  326. .Add(x => x.JobScope.ID)
  327. .Add(x => x.JobScope.Number))
  328. .ToObjects<TEntity>().First();
  329. if (entity.JobLink.ID == Guid.Empty)
  330. {
  331. MessageBox.Show($"{typeof(TEntity).Name} is not linked to a job. Please select a job first.");
  332. return;
  333. }
  334. var window = new MultiSelectDialog<JobScope>(
  335. new Filters<JobScope>()
  336. .Add(LookupFactory.DefineLookupFilter<TEntity, JobScope, JobScopeLink>(x => x.JobScope, new TEntity[] { entity }))
  337. .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(entity.JobLink.ID))
  338. .Combine(),
  339. Columns.None<JobScope>().Add(x => x.ID).Add(x => x.Number),
  340. multiselect: false);
  341. if (!window.ShowDialog(nameof(JobScope.Number), entity.JobScope.Number, Syncfusion.Data.FilterType.Equals))
  342. {
  343. return;
  344. }
  345. var scope = window.Data().ToObjects<JobScope>().First();
  346. entity.JobScope.ID = scope.ID;
  347. Client.Save(entity, "Linked scope set by user from Digital forms dock.");
  348. MessageBox.Show($"{typeof(TEntity).Name} has been assigned to scope {scope.Number}.");
  349. }
  350. private static readonly MethodInfo SetJob_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJob_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
  351. private static void SetJob_Click<TEntity>(DigitalFormDockModel formModel)
  352. where TEntity: Entity, IJobScopedItem, IRemotable, IPersistent, new()
  353. {
  354. var entity = Client.Query(
  355. new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
  356. LookupFactory.DefineLookupFilterColumns<TEntity, Job, JobLink>(x => x.JobLink)
  357. .Add(x => x.ID)
  358. .Add(x => x.JobLink.ID)
  359. .Add(x => x.JobLink.JobNumber))
  360. .ToObjects<TEntity>().First();
  361. var window = new MultiSelectDialog<Job>(
  362. LookupFactory.DefineLookupFilter<TEntity, Job, JobLink>(x => x.JobLink, new TEntity[] { entity }),
  363. Columns.None<Job>().Add(x => x.DefaultScope.ID).Add(x => x.JobNumber),
  364. multiselect: false);
  365. if (!window.ShowDialog(nameof(Job.JobNumber), entity.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals))
  366. {
  367. return;
  368. }
  369. var job = window.Data().ToObjects<Job>().First();
  370. entity.JobLink.ID = job.ID;
  371. entity.JobLink.Synchronise(job);
  372. Client.Save(entity, "Linked job set by user from Digital forms dock.");
  373. MessageBox.Show($"{typeof(TEntity).Name} has been assigned to job {job.JobNumber}.");
  374. }
  375. private void MenuBuild(DynamicMenuColumn column, CoreRow? row)
  376. {
  377. if (row is null) return;
  378. var form = row.ToObject<DigitalFormDockModel>();
  379. var linkType = DFUtils.FormEntityLinkType(form.FormType);
  380. var entityType = DFUtils.FormEntityType(form.FormType);
  381. MenuBuildGenericMethod.MakeGenericMethod(form.FormType, entityType, linkType).Invoke(this, new object?[] { column.GetMenu(), form });
  382. }
  383. private DynamicMenuStatus MenuStatus(CoreRow row)
  384. {
  385. if (row == null) return DynamicMenuStatus.Hidden;
  386. return DynamicMenuStatus.Enabled;
  387. }
  388. protected override void Reload(
  389. Filters<DigitalFormDockModel> criteria, Columns<DigitalFormDockModel> columns, ref SortOrder<DigitalFormDockModel>? sort,
  390. CancellationToken token, Action<CoreTable?, Exception?> action)
  391. {
  392. var queryDefs = new Dictionary<string, IQueryDef>();
  393. var types = DFUtils.GetFormInstanceTypes().Except(ExcludedTypes).ToList();
  394. foreach (var type in types)
  395. {
  396. var filter = Filter.Create<IDigitalFormInstance>(type, x => x.FormCompleted).IsGreaterThanOrEqualTo(StartDate)
  397. .And<IDigitalFormInstance>(x => x.FormCancelled).IsEqualTo(DateTime.MinValue);
  398. var cols = Columns.Create<IDigitalFormInstance>(type, ColumnTypeFlags.None)
  399. .Add<IDigitalFormInstance>(c => c.ID)
  400. .Add<IDigitalFormInstance>(c => c.Parent.ID)
  401. .Add<IDigitalFormInstance>(c => c.Form.ID)
  402. .Add<IDigitalFormInstance>(c => c.Number)
  403. .Add<IDigitalFormInstance>(c => c.Description)
  404. .Add<IDigitalFormInstance>(c => c.FormCompleted)
  405. .Add<IDigitalFormInstance>(c => c.FormCompletedBy.UserID)
  406. .Add<IDigitalFormInstance>(c => c.FormProcessed);
  407. var sorts = SortOrder.Create<IDigitalFormInstance>(type, x => x.FormCompleted, SortDirection.Descending);
  408. queryDefs.Add(
  409. type.ToString(),
  410. new QueryDef(type)
  411. {
  412. Filter = filter,
  413. Columns = cols,
  414. SortOrder = sorts
  415. });
  416. }
  417. Client.QueryMultiple(
  418. (results, e) =>
  419. {
  420. if(results is not null)
  421. {
  422. var data = new CoreTable();
  423. data.LoadColumns(typeof(DigitalFormDockModel));
  424. foreach (var type in types)
  425. data.LoadFrom(
  426. results[type.ToString()],
  427. _mappings,
  428. (r) => r.Set<DigitalFormDockModel, Type>(x => x.FormType, type)
  429. );
  430. action.Invoke(data, null);
  431. }
  432. else if(e is not null)
  433. {
  434. Logger.Send(LogType.Error, ClientFactory.UserID, CoreUtils.FormatException(e));
  435. MessageBox.Show($"Error: {e.Message}");
  436. }
  437. },
  438. queryDefs);
  439. }
  440. public override DigitalFormDockModel LoadItem(CoreRow row)
  441. {
  442. return row.ToObject<DigitalFormDockModel>();
  443. }
  444. public override void SaveItem(DigitalFormDockModel item)
  445. {
  446. }
  447. public override void DeleteItems(params CoreRow[] rows)
  448. {
  449. }
  450. protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  451. {
  452. base.DoDoubleClick(sender, args);
  453. var row = SelectedRows.FirstOrDefault();
  454. if (row is null)
  455. return;
  456. var instanceID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
  457. var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
  458. var formType = row.Get<DigitalFormDockModel, Type>(x => x.FormType);
  459. var formInstance = Client.Create(formType)
  460. .Query(
  461. Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(instanceID),
  462. DynamicFormEditWindow.FormColumns(formType))
  463. .Rows.FirstOrDefault()
  464. ?.ToObject(formType) as IDigitalFormInstance;
  465. if (formInstance is null)
  466. {
  467. return;
  468. }
  469. if (formID == Guid.Empty)
  470. {
  471. var window = new DeletedFormWindow();
  472. window.FormData = formInstance?.FormData ?? "";
  473. window.ShowDialog();
  474. return;
  475. }
  476. if (DynamicFormEditWindow.EditDigitalForm(formInstance, out var model))
  477. {
  478. model.Update(null);
  479. }
  480. }
  481. }