DigitalFormDockGrid.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  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. if (new DynamicDataGrid<JobScope>().EditItems(new[] { scope }))
  264. {
  265. UpdateScopeID(model, scope.ID);
  266. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  267. // Update the form scope here (from the SetScope) stuff
  268. }
  269. }
  270. }
  271. private void SetJobScopeFromMenu_Click(object sender, RoutedEventArgs e)
  272. {
  273. if (sender is MenuItem mi && mi.DataContext is DigitalFormDockModel model && mi.Tag is JobScope scope)
  274. {
  275. UpdateScopeID(model, scope.ID);
  276. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  277. // Update the form scope here (from the SetScope) stuff
  278. }
  279. }
  280. private static void ViewEntity_Click<TEntity>(DigitalFormDockModel model)
  281. where TEntity : Entity, IRemotable, IPersistent, new()
  282. {
  283. var entity = Client.Query(
  284. new Filter<TEntity>(x => x.ID).IsEqualTo(model.ParentID),
  285. DynamicGridUtils.LoadEditorColumns<TEntity>(Columns.None<TEntity>()))
  286. .ToObjects<TEntity>().First();
  287. var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicGrid<>), typeof(TEntity)) as DynamicGrid<TEntity>)!;
  288. grid.EditItems(new TEntity[] { entity });
  289. }
  290. private static void SetJobScopeFromJob_Click(DigitalFormDockModel formModel)
  291. {
  292. var instance = Client.Query(
  293. new Filter<JobForm>(x => x.ID).IsEqualTo(formModel.ID),
  294. LookupFactory.DefineLookupFilterColumns<JobForm, JobScope, JobScopeLink>(x => x.JobScope)
  295. .Add(x => x.ID)
  296. .Add(x => x.Parent.ID))
  297. .ToObjects<JobForm>().First();
  298. var job = Client.Query(
  299. new Filter<Job>(x => x.ID).IsEqualTo(instance.Parent.ID),
  300. LookupFactory.DefineChildFilterColumns<Job, JobScope>()).ToObjects<Job>().First();
  301. var window = new MultiSelectDialog<JobScope>(
  302. new Filters<JobScope>()
  303. .Add(LookupFactory.DefineChildFilter<Job, JobScope>(new Job[] { job }))
  304. .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(instance.Parent.ID))
  305. .Combine(),
  306. Columns.None<JobScope>().Add(x => x.ID).Add(x => x.Number), multiselect: false);
  307. if (!window.ShowDialog())
  308. {
  309. return;
  310. }
  311. var scope = window.Data().ToObjects<JobScope>().First();
  312. instance.JobScope.ID = scope.ID;
  313. Client.Save(instance, "Linked scope set by user from Digital forms dock.");
  314. MessageBox.Show($"Form has been assigned to scope {scope.Number}.");
  315. }
  316. private static readonly MethodInfo SetJobScope_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJobScope_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
  317. private static void SetJobScope_Click<TEntity>(DigitalFormDockModel formModel)
  318. where TEntity : Entity, IJobScopedItem, IRemotable, IPersistent, new()
  319. {
  320. var entity = Client.Query(
  321. new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
  322. LookupFactory.DefineLookupFilterColumns<TEntity, JobScope, JobScopeLink>(x => x.JobScope)
  323. .Add(x => x.ID)
  324. .Add(x => x.JobLink.ID)
  325. .Add(x => x.JobScope.ID)
  326. .Add(x => x.JobScope.Number))
  327. .ToObjects<TEntity>().First();
  328. if (entity.JobLink.ID == Guid.Empty)
  329. {
  330. MessageBox.Show($"{typeof(TEntity).Name} is not linked to a job. Please select a job first.");
  331. return;
  332. }
  333. var window = new MultiSelectDialog<JobScope>(
  334. new Filters<JobScope>()
  335. .Add(LookupFactory.DefineLookupFilter<TEntity, JobScope, JobScopeLink>(x => x.JobScope, new TEntity[] { entity }))
  336. .Add(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(entity.JobLink.ID))
  337. .Combine(),
  338. Columns.None<JobScope>().Add(x => x.ID).Add(x => x.Number),
  339. multiselect: false);
  340. if (!window.ShowDialog(nameof(JobScope.Number), entity.JobScope.Number, Syncfusion.Data.FilterType.Equals))
  341. {
  342. return;
  343. }
  344. var scope = window.Data().ToObjects<JobScope>().First();
  345. entity.JobScope.ID = scope.ID;
  346. Client.Save(entity, "Linked scope set by user from Digital forms dock.");
  347. MessageBox.Show($"{typeof(TEntity).Name} has been assigned to scope {scope.Number}.");
  348. }
  349. private static readonly MethodInfo SetJob_ClickMethod = typeof(DigitalFormDockGrid).GetMethod(nameof(SetJob_Click), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!;
  350. private static void SetJob_Click<TEntity>(DigitalFormDockModel formModel)
  351. where TEntity: Entity, IJobScopedItem, IRemotable, IPersistent, new()
  352. {
  353. var entity = Client.Query(
  354. new Filter<TEntity>(x => x.ID).IsEqualTo(formModel.ParentID),
  355. LookupFactory.DefineLookupFilterColumns<TEntity, Job, JobLink>(x => x.JobLink)
  356. .Add(x => x.ID)
  357. .Add(x => x.JobLink.ID)
  358. .Add(x => x.JobLink.JobNumber))
  359. .ToObjects<TEntity>().First();
  360. var window = new MultiSelectDialog<Job>(
  361. LookupFactory.DefineLookupFilter<TEntity, Job, JobLink>(x => x.JobLink, new TEntity[] { entity }),
  362. Columns.None<Job>().Add(x => x.DefaultScope.ID).Add(x => x.JobNumber),
  363. multiselect: false);
  364. if (!window.ShowDialog(nameof(Job.JobNumber), entity.JobLink.JobNumber, Syncfusion.Data.FilterType.Equals))
  365. {
  366. return;
  367. }
  368. var job = window.Data().ToObjects<Job>().First();
  369. entity.JobLink.ID = job.ID;
  370. entity.JobLink.Synchronise(job);
  371. Client.Save(entity, "Linked job set by user from Digital forms dock.");
  372. MessageBox.Show($"{typeof(TEntity).Name} has been assigned to job {job.JobNumber}.");
  373. }
  374. private void MenuBuild(DynamicMenuColumn column, CoreRow? row)
  375. {
  376. if (row is null) return;
  377. var form = row.ToObject<DigitalFormDockModel>();
  378. var linkType = DFUtils.FormEntityLinkType(form.FormType);
  379. var entityType = DFUtils.FormEntityType(form.FormType);
  380. MenuBuildGenericMethod.MakeGenericMethod(form.FormType, entityType, linkType).Invoke(this, new object?[] { column.GetMenu(), form });
  381. }
  382. private DynamicMenuStatus MenuStatus(CoreRow row)
  383. {
  384. if (row == null) return DynamicMenuStatus.Hidden;
  385. return DynamicMenuStatus.Enabled;
  386. }
  387. protected override void Reload(
  388. Filters<DigitalFormDockModel> criteria, Columns<DigitalFormDockModel> columns, ref SortOrder<DigitalFormDockModel>? sort,
  389. CancellationToken token, Action<CoreTable?, Exception?> action)
  390. {
  391. var queryDefs = new Dictionary<string, IQueryDef>();
  392. var types = DFUtils.GetFormInstanceTypes().Except(ExcludedTypes).ToList();
  393. foreach (var type in types)
  394. {
  395. var filter = Filter.Create<IDigitalFormInstance>(type, x => x.FormCompleted).IsGreaterThanOrEqualTo(StartDate)
  396. .And<IDigitalFormInstance>(x => x.FormCancelled).IsEqualTo(DateTime.MinValue);
  397. var cols = Columns.Create<IDigitalFormInstance>(type, ColumnTypeFlags.None)
  398. .Add<IDigitalFormInstance>(c => c.ID)
  399. .Add<IDigitalFormInstance>(c => c.Parent.ID)
  400. .Add<IDigitalFormInstance>(c => c.Form.ID)
  401. .Add<IDigitalFormInstance>(c => c.Number)
  402. .Add<IDigitalFormInstance>(c => c.Description)
  403. .Add<IDigitalFormInstance>(c => c.FormCompleted)
  404. .Add<IDigitalFormInstance>(c => c.FormCompletedBy.UserID)
  405. .Add<IDigitalFormInstance>(c => c.FormProcessed);
  406. var sorts = SortOrder.Create<IDigitalFormInstance>(type, x => x.FormCompleted, SortDirection.Descending);
  407. queryDefs.Add(
  408. type.ToString(),
  409. new QueryDef(type)
  410. {
  411. Filter = filter,
  412. Columns = cols,
  413. SortOrder = sorts
  414. });
  415. }
  416. Client.QueryMultiple(
  417. (results, e) =>
  418. {
  419. if(results is not null)
  420. {
  421. var data = new CoreTable();
  422. data.LoadColumns(typeof(DigitalFormDockModel));
  423. foreach (var type in types)
  424. data.LoadFrom(
  425. results[type.ToString()],
  426. _mappings,
  427. (r) => r.Set<DigitalFormDockModel, Type>(x => x.FormType, type)
  428. );
  429. action.Invoke(data, null);
  430. }
  431. else if(e is not null)
  432. {
  433. Logger.Send(LogType.Error, ClientFactory.UserID, CoreUtils.FormatException(e));
  434. MessageBox.Show($"Error: {e.Message}");
  435. }
  436. },
  437. queryDefs);
  438. }
  439. public override DigitalFormDockModel LoadItem(CoreRow row)
  440. {
  441. return row.ToObject<DigitalFormDockModel>();
  442. }
  443. public override void SaveItem(DigitalFormDockModel item)
  444. {
  445. }
  446. public override void DeleteItems(params CoreRow[] rows)
  447. {
  448. }
  449. protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  450. {
  451. base.DoDoubleClick(sender, args);
  452. var row = SelectedRows.FirstOrDefault();
  453. if (row is null)
  454. return;
  455. var instanceID = row.Get<DigitalFormDockModel, Guid>(x => x.ID);
  456. var formID = row.Get<DigitalFormDockModel, Guid>(x => x.FormID);
  457. var formType = row.Get<DigitalFormDockModel, Type>(x => x.FormType);
  458. var formInstance = Client.Create(formType)
  459. .Query(
  460. Filter.Create<IDigitalFormInstance>(formType, x => x.ID).IsEqualTo(instanceID),
  461. DynamicFormEditWindow.FormColumns(formType))
  462. .Rows.FirstOrDefault()
  463. ?.ToObject(formType) as IDigitalFormInstance;
  464. if (formInstance is null)
  465. {
  466. return;
  467. }
  468. if (formID == Guid.Empty)
  469. {
  470. var window = new DeletedFormWindow();
  471. window.FormData = formInstance?.FormData ?? "";
  472. window.ShowDialog();
  473. return;
  474. }
  475. if (DynamicFormEditWindow.EditDigitalForm(formInstance, out var model))
  476. {
  477. model.Update(null);
  478. }
  479. }
  480. }