DatabaseUpdateScripts.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. using Comal.Classes;
  2. using InABox.Core;
  3. using InABox.Database;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Linq.Expressions;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. using Syncfusion.Windows.Tools.Controls;
  11. using System.Diagnostics.CodeAnalysis;
  12. using System.Reflection;
  13. namespace PRS.Shared
  14. {
  15. public static class DatabaseUpdateScripts
  16. {
  17. public static void RegisterScripts()
  18. {
  19. DataUpdater.RegisterUpdateScript("6.31", Update_6_31);
  20. DataUpdater.RegisterUpdateScript("6.37", Update_6_37);
  21. DataUpdater.RegisterUpdateScript("6.38", Update_6_38);
  22. DataUpdater.RegisterUpdateScript("6.39", Update_6_39);
  23. DataUpdater.RegisterUpdateScript("6.43", Update_6_43);
  24. DataUpdater.RegisterUpdateScript("7.00", Update_7_00);
  25. DataUpdater.RegisterUpdateScript("7.06", Update_7_06);
  26. }
  27. private static Dictionary<string, Tuple<string, string>> _6_31_module_map = new()
  28. {
  29. { "Assignments", new("Assignments", "Assignments") },
  30. { "Daily Report", new("Daily Report", "Assignments") },
  31. { "Delivered On Site", new("Delivered On Site", "Delivery Items") },
  32. { "Deliveries", new("Deliveries", "Deliveries") },
  33. { "Digital Forms", new("Digital Forms", "DigitalForm") },
  34. { "Employee List", new("Employees", "Employee") },
  35. { "Equipment List", new("Equipment", "Equipment") },
  36. { "Factory Floor", new("Factory", "Manufacturing Packets") },
  37. { "Incoming Consignments", new("Consignments", "Consignment") },
  38. { "Manufacturing Status", new("Manufacturing Packets", "Manufacturing Packets") },
  39. { "Product List", new("Products", "Products") },
  40. { "Projects", new("Job Details", "Job Details") },
  41. { "Purchase Orders", new("Purchase Orders", "PurchaseOrder") },
  42. { "Quotes", new("Quotes", "Quotes") },
  43. { "Rack List", new("Shipping", "Shipments") },
  44. { "Site Requisitions", new("Requisitions", "Requisition") },
  45. { "Staff TimeSheets", new("Timesheets", "TimeSheet") },
  46. { "Stock Locations", new("Stock Locations", "StockLocation") },
  47. { "Stock Movements", new("Stock Movements", "StockMovement") },
  48. { "Task List", new("Tasks By Status", "Kanban") },
  49. };
  50. private static bool Update_6_31()
  51. {
  52. var modules = DbFactory.Provider.Query(new Filter<CustomModule>().All())
  53. .Rows.Select(x => x.ToObject<CustomModule>()).ToList();
  54. foreach(var module in modules)
  55. {
  56. if (!string.IsNullOrWhiteSpace(module.Section))
  57. {
  58. if (_6_31_module_map.TryGetValue(module.Section, out var map))
  59. {
  60. module.Section = map.Item1;
  61. module.DataModel = map.Item2;
  62. module.AllRecords = true;
  63. }
  64. else
  65. {
  66. Logger.Send(LogType.Error, "", $"Custom Module '{module.Name}' has section name '{module.Section}' and will no longer be visible!");
  67. }
  68. }
  69. }
  70. DbFactory.Provider.Save(modules);
  71. return true;
  72. }
  73. private static bool Update_6_37()
  74. {
  75. Logger.Send(LogType.Information, "", "Recreating views");
  76. DbFactory.Provider.ForceRecreateViews();
  77. return true;
  78. }
  79. private static bool Update_6_38()
  80. {
  81. Logger.Send(LogType.Information, "", "Converting Job Requisition Dates to Due Dates");
  82. List<JobRequisition> updates = new List<JobRequisition>();
  83. var columns = new Columns<JobRequisition>(x => x.ID);
  84. columns.Add("Date");
  85. CoreTable requis = DbFactory.Provider.Query<JobRequisition>(null, columns);
  86. foreach (var row in requis.Rows)
  87. {
  88. var requi = row.ToObject<JobRequisition>();
  89. requi.Approved = row.Get<DateTime>("Date");
  90. updates.Add(requi);
  91. }
  92. DbFactory.Provider.Save(updates);
  93. return true;
  94. }
  95. private static bool Update_6_39()
  96. {
  97. void ConvertJobDocumentIssuedDates()
  98. {
  99. Logger.Send(LogType.Information, "", "Converting Job Document Issued Dates");
  100. List<JobDocumentSetMileStone> updates = new List<JobDocumentSetMileStone>();
  101. var columns = new Columns<JobDocumentSetMileStone>(x => x.ID).Add(x => x.Submitted).Add(x => x.Status);
  102. columns.Add("Issued");
  103. CoreTable milestones = DbFactory.Provider.Query<JobDocumentSetMileStone>(null, columns);
  104. foreach (var row in milestones.Rows)
  105. {
  106. var milestone = row.ToObject<JobDocumentSetMileStone>();
  107. if (milestone.Status == JobDocumentSetMileStoneStatus.Unknown)
  108. milestone.Status = JobDocumentSetMileStoneStatus.Submitted;
  109. milestone.Submitted = row.Get<DateTime>("Issued");
  110. updates.Add(milestone);
  111. }
  112. DbFactory.Provider.Save(updates);
  113. }
  114. void ConvertProductUnitsOfMeasure()
  115. {
  116. Logger.Send(LogType.Information, "", "Converting Product Units of Measure");
  117. List<ProductDimensionUnit> updates = new List<ProductDimensionUnit>();
  118. var columns = new Columns<ProductDimensionUnit>(x => x.ID).Add(x => x.Description);
  119. CoreTable units = DbFactory.Provider.Query<ProductDimensionUnit>(new Filter<ProductDimensionUnit>(x=>x.Code).IsEqualTo(""), columns);
  120. foreach (var row in units.Rows)
  121. {
  122. var unit = row.ToObject<ProductDimensionUnit>();
  123. unit.Code = unit.Description;
  124. updates.Add(unit);
  125. }
  126. DbFactory.Provider.Save(updates);
  127. }
  128. void ConvertQuoteUnitsOfMeasure()
  129. {
  130. Logger.Send(LogType.Information, "", "Converting Quote Units of Measure");
  131. List<QuoteTakeOffUnit> updates = new List<QuoteTakeOffUnit>();
  132. var columns = new Columns<QuoteTakeOffUnit>(x => x.ID).Add(x => x.Description);
  133. CoreTable units = DbFactory.Provider.Query<QuoteTakeOffUnit>(new Filter<QuoteTakeOffUnit>(x=>x.Code).IsEqualTo(""), columns);
  134. foreach (var row in units.Rows)
  135. {
  136. var unit = row.ToObject<QuoteTakeOffUnit>();
  137. unit.Code = unit.Description;
  138. updates.Add(unit);
  139. }
  140. DbFactory.Provider.Save(updates);
  141. }
  142. ConvertJobDocumentIssuedDates();
  143. ConvertProductUnitsOfMeasure();
  144. ConvertQuoteUnitsOfMeasure();
  145. return true;
  146. }
  147. private static bool Update_6_43()
  148. {
  149. void ConvertSupplierProductLinks()
  150. {
  151. Logger.Send(LogType.Information, "", "Converting Supplier/Product Links");
  152. List<SupplierProduct> updates = new List<SupplierProduct>();
  153. var columns = new Columns<SupplierProduct>(x => x.ID).Add(x=>x.Product.ID);
  154. columns.Add("ProductLink.ID");
  155. CoreTable products = DbFactory.Provider.Query<SupplierProduct>(null, columns);
  156. foreach (var row in products.Rows)
  157. {
  158. Guid id = row.Get<SupplierProduct,Guid>(x=>x.ID);
  159. Guid oldid = row.Get<Guid>("ProductLink.ID");
  160. Guid newid = row.Get<SupplierProduct,Guid>(x=>x.Product.ID);
  161. if ((oldid != Guid.Empty) && (newid == Guid.Empty))
  162. {
  163. var update = new SupplierProduct() { ID = id };
  164. update.CommitChanges();
  165. update.Product.ID = oldid;
  166. updates.Add(update);
  167. }
  168. }
  169. DbFactory.Provider.Save(updates);
  170. }
  171. ConvertSupplierProductLinks();
  172. return true;
  173. }
  174. private struct Map<T>
  175. {
  176. public String Old;
  177. public Expression<Func<T, object>> New;
  178. public Map(String oldcolumn, Expression<Func<T, object>> newcolumn)
  179. {
  180. Old = oldcolumn;
  181. New = newcolumn;
  182. }
  183. }
  184. private static bool Update_7_00()
  185. {
  186. static void Convert<T>(
  187. Filter<T> filter,
  188. params Map<T>[] maps
  189. ) where T : Entity, IPersistent, IRemotable, new()
  190. {
  191. Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()}...");
  192. List<T> updates = new List<T>();
  193. var columns = new Columns<T>(x => x.ID);
  194. foreach (var map in maps)
  195. {
  196. if (!columns.Items.Any(x=>String.Equals(x.Property,map.Old)))
  197. columns.Add(map.Old);
  198. if (!columns.Items.Any(x=>String.Equals(x.Property,CoreUtils.GetFullPropertyName<T, object>(map.New, "."))))
  199. columns.Add(map.New);
  200. }
  201. CoreTable table = DbFactory.Provider.Query<T>(filter,columns);
  202. int iCount = 0;
  203. foreach (var row in table.Rows)
  204. {
  205. var update = row.ToObject<T>();
  206. foreach (var map in maps)
  207. CoreUtils.SetPropertyValue(update, CoreUtils.GetFullPropertyName<T, object>(map.New, "."), CoreUtils.GetPropertyValue(update, map.Old));
  208. if (update.IsChanged())
  209. updates.Add(update);
  210. if (updates.Count == 100)
  211. {
  212. iCount += updates.Count;
  213. Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()} Times ({iCount}/{table.Rows.Count}");
  214. DbFactory.Provider.Save(updates);
  215. updates.Clear();
  216. }
  217. }
  218. if (updates.Count > 0)
  219. {
  220. iCount += updates.Count;
  221. Logger.Send(LogType.Information, "", $"Converting {typeof(T).EntityName().Split('.').Last()} Times ({iCount}/{table.Rows.Count})");
  222. DbFactory.Provider.Save(updates);
  223. updates.Clear();
  224. }
  225. }
  226. Convert<Assignment>(
  227. new Filter<Assignment>(x=>x.Booked.Start).IsEqualTo(DateTime.MinValue)
  228. .And(x=>x.Booked.Finish).IsEqualTo(DateTime.MinValue)
  229. .And(x=>x.Actual.Finish).IsEqualTo(DateTime.MinValue)
  230. .And(x=>x.Actual.Finish).IsEqualTo(DateTime.MinValue),
  231. new Map<Assignment>("Start",x => x.Booked.Start),
  232. new Map<Assignment>("Finish",x => x.Booked.Finish),
  233. new Map<Assignment>("Start",x => x.Actual.Start),
  234. new Map<Assignment>("Finish",x => x.Actual.Finish)
  235. );
  236. // ConvertTimes<TimeSheet>(
  237. // x => x.Actual.Duration,
  238. // new TimeExpressions<TimeSheet>(x => x.Actual.Start, x => x.Actual.Finish),
  239. // new TimeExpressions<TimeSheet>(x => x.Approved.Start, x => x.Approved.Finish)
  240. // );
  241. void Convert_StandardLeaves_and_LeaveRequests()
  242. {
  243. // Delete from TimeSheet where processed={} and leaverequestlink.id != empty
  244. var unprocessedtimesheets = DbFactory.Provider.Query<TimeSheet>(
  245. new Filter<TimeSheet>(x => x.Processed).IsEqualTo(DateTime.MinValue)
  246. .And(x => x.LeaveRequestLink.ID).IsNotEqualTo(Guid.Empty),
  247. new Columns<TimeSheet>(x=>x.ID)
  248. ).Rows.Select(x=>x.ToObject<TimeSheet>()).ToArray();
  249. int iTimes = 0;
  250. while (iTimes < unprocessedtimesheets.Length)
  251. {
  252. var deletions = unprocessedtimesheets.Skip(iTimes).Take(100).ToArray();
  253. DbFactory.Provider.Purge<TimeSheet>(deletions);
  254. iTimes += deletions.Length;
  255. }
  256. //DbFactory.Provider.Delete<TimeSheet>(unprocessedtimesheets,"");
  257. // Find all Leave Requests where public holiday != empty
  258. var standardleaverequests = DbFactory.Provider.Query<LeaveRequest>(
  259. new Filter<LeaveRequest>(x => x.PublicHoliday.ID).IsNotEqualTo(Guid.Empty),
  260. new Columns<LeaveRequest>(x=>x.ID)
  261. .Add(x=>x.PublicHoliday.ID)
  262. ).Rows.Select(x => x.ToObject<LeaveRequest>()).ToArray();
  263. foreach (var standardleaverequest in standardleaverequests)
  264. {
  265. // Find all timesheets for this leave request
  266. var standardleavetimesheets = DbFactory.Provider.Query<TimeSheet>(
  267. new Filter<TimeSheet>(x=>x.LeaveRequestLink.ID).IsEqualTo(standardleaverequest.ID),
  268. new Columns<TimeSheet>(x=>x.ID)
  269. .Add(x=>x.LeaveRequestLink.ID)
  270. ).Rows.Select(x=>x.ToObject<TimeSheet>()).ToArray();
  271. // Redirect timesheet from leaverequest to standardleave
  272. foreach (var standardleavetimesheet in standardleavetimesheets)
  273. {
  274. standardleavetimesheet.StandardLeaveLink.ID = standardleaverequest.PublicHoliday.ID;
  275. standardleavetimesheet.LeaveRequestLink.ID = Guid.Empty;
  276. }
  277. if (standardleavetimesheets.Any())
  278. DbFactory.Provider.Save(standardleavetimesheets);
  279. }
  280. // delete these leave requests
  281. int iRequests = 0;
  282. while (iRequests < standardleaverequests.Length)
  283. {
  284. var deletions = standardleaverequests.Skip(iRequests).Take(100).ToArray();
  285. DbFactory.Provider.Purge<LeaveRequest>(deletions);
  286. iRequests += deletions.Length;
  287. }
  288. // Delete from Assignment where leaverequestlink id != empty
  289. var leaveassignments = DbFactory.Provider.Query<Assignment>(
  290. new Filter<Assignment>(x => x.LeaveRequestLink.ID).IsNotEqualTo(Guid.Empty),
  291. new Columns<Assignment>(x=>x.ID)
  292. ).Rows.Select(x=>x.ToObject<Assignment>()).ToArray();
  293. int iAssignments = 0;
  294. while (iAssignments < leaveassignments.Length)
  295. {
  296. var deletions = leaveassignments.Skip(iAssignments).Take(100).ToArray();
  297. DbFactory.Provider.Purge<Assignment>(deletions);
  298. iAssignments += deletions.Length;
  299. }
  300. }
  301. Convert_StandardLeaves_and_LeaveRequests();
  302. return true;
  303. }
  304. private class Update_7_06_Class
  305. {
  306. private static Dictionary<Type, List<Tuple<Type, string>>> _cascades = new();
  307. private static Dictionary<Type, List<Tuple<Type, List<string>>>> _setNulls = new();
  308. private static void LoadDeletions(Type type)
  309. {
  310. if (_cascades.ContainsKey(type)) return;
  311. // Get the EntityLink that is associated with this class
  312. var linkclass = CoreUtils.TypeList(
  313. new[] { type.Assembly },
  314. x => typeof(IEntityLink).GetTypeInfo().IsAssignableFrom(x) && x.GetInheritedGenericTypeArguments().FirstOrDefault() == type
  315. ).FirstOrDefault();
  316. // if The entitylink does not exist, we don't need to do anything
  317. if (linkclass == null)
  318. return;
  319. var cascades = new List<Tuple<Type, string>>();
  320. var setNulls = new List<Tuple<Type, List<string>>>();
  321. var childtypes = DbFactory.Provider.Types.Where(x => x.IsSubclassOf(typeof(Entity)) && x.GetCustomAttribute<AutoEntity>() == null);
  322. foreach (var childtype in childtypes)
  323. {
  324. // Get all registered types for this entitylink
  325. var fields = new List<string>();
  326. var bDelete = false;
  327. // Find any IEntityLink<> properties that refer back to this class
  328. var childprops = CoreUtils.PropertyList(childtype, x => x.PropertyType == linkclass);
  329. foreach (var childprop in childprops)
  330. {
  331. var fieldname = string.Format("{0}.ID", childprop.Name);
  332. var attr = childprop.GetCustomAttributes(typeof(EntityRelationshipAttribute), true).FirstOrDefault();
  333. if (attr != null && ((EntityRelationshipAttribute)attr).Action.Equals(DeleteAction.Cascade))
  334. {
  335. cascades.Add(new(childtype, fieldname));
  336. bDelete = true;
  337. break;
  338. }
  339. fields.Add(fieldname);
  340. }
  341. if (!bDelete && fields.Any())
  342. {
  343. setNulls.Add(new(childtype, fields));
  344. }
  345. }
  346. _cascades[type] = cascades;
  347. _setNulls[type] = setNulls;
  348. }
  349. private static bool GetCascades(Type type, [NotNullWhen(true)] out List<Tuple<Type, string>>? cascades)
  350. {
  351. LoadDeletions(type);
  352. return _cascades.TryGetValue(type, out cascades);
  353. }
  354. private static bool GetSetNulls(Type type, [NotNullWhen(true)] out List<Tuple<Type, List<string>>>? setNulls)
  355. {
  356. LoadDeletions(type);
  357. return _setNulls.TryGetValue(type, out setNulls);
  358. }
  359. private static MethodInfo _deleteEntitiesMethod = typeof(Update_7_06_Class).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
  360. .Single(x => x.Name == nameof(DeleteEntity) && x.IsGenericMethod);
  361. private static void DeleteEntity<T>(Deletion deletion, Guid parentID, string parentField, DeletionData deletionData) where T : Entity, new()
  362. {
  363. var columns = DeletionData.DeletionColumns<T>();
  364. var delEntities = DbFactory.Provider.QueryDeleted(deletion, new Filter<T>(parentField).IsEqualTo(parentID), columns);
  365. var nDelntities = DbFactory.Provider.Query(new Filter<T>(parentField).IsEqualTo(parentID), columns);
  366. foreach (var row in delEntities.Rows.Concat(nDelntities.Rows))
  367. {
  368. deletionData.DeleteEntity<T>(row);
  369. CascadeDelete(typeof(T), deletion, row.Get<T, Guid>(x => x.ID), deletionData);
  370. }
  371. }
  372. private static void DeleteEntity(Type T, Deletion deletion, Guid parentID, string parentField, DeletionData deletionData)
  373. {
  374. _deleteEntitiesMethod.MakeGenericMethod(T).Invoke(null, new object?[] { deletion, parentID, parentField, deletionData });
  375. }
  376. private static MethodInfo _setNullEntityMethod = typeof(Update_7_06_Class).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
  377. .Single(x => x.Name == nameof(SetNullEntity) && x.IsGenericMethod);
  378. private static void SetNullEntity<T>(List<string> properties, Guid parentID, DeletionData deletionData) where T : Entity, new()
  379. {
  380. foreach (var property in properties)
  381. {
  382. var entities = DbFactory.Provider.Query(new Filter<T>(property).IsEqualTo(parentID), new Columns<T>(x => x.ID));
  383. foreach (var row in entities.Rows)
  384. {
  385. deletionData.SetNullEntity<T>(row.Get<T, Guid>(x => x.ID), property, parentID);
  386. }
  387. }
  388. }
  389. private static void SetNullEntity(Type T, List<string> properties, Guid parentID, DeletionData deletionData)
  390. {
  391. _setNullEntityMethod.MakeGenericMethod(T).Invoke(null, new object?[] { properties, parentID, deletionData });
  392. }
  393. private static void CascadeDelete(Type type, Deletion deletion, Guid parentID, DeletionData deletionData)
  394. {
  395. if (GetCascades(type, out var cascades))
  396. {
  397. foreach (var cascade in cascades)
  398. {
  399. DeleteEntity(cascade.Item1, deletion, parentID, cascade.Item2, deletionData);
  400. }
  401. }
  402. if (GetSetNulls(type, out var setNulls))
  403. {
  404. foreach (var setNull in setNulls)
  405. {
  406. SetNullEntity(setNull.Item1, setNull.Item2, parentID, deletionData);
  407. }
  408. }
  409. }
  410. // Referenced via reflection.
  411. private static void PurgeEntityType<T>(Deletion deletion) where T : Entity, new()
  412. {
  413. var entities = DbFactory.Provider.QueryDeleted(deletion, null, DeletionData.DeletionColumns<T>()).ToList<T>();
  414. var deletionData = new DeletionData();
  415. foreach (var entity in entities)
  416. {
  417. deletionData.DeleteEntity(entity);
  418. CascadeDelete(typeof(T), deletion, entity.ID, deletionData);
  419. }
  420. if (deletionData.Cascades.Count > 0 || deletionData.SetNulls.Count > 0)
  421. {
  422. var tableName = typeof(T).Name;
  423. var newDeletion = new Deletion()
  424. {
  425. DeletionDate = DateTime.Now,
  426. HeadTable = tableName,
  427. Description = $"Deleted {entities.Count} entries",
  428. DeletedBy = deletion.DeletedBy,
  429. Data = Serialization.Serialize(deletionData)
  430. };
  431. DbFactory.Provider.Save(newDeletion);
  432. }
  433. DbFactory.Provider.Purge(entities);
  434. }
  435. public static void Purge(Deletion deletion)
  436. {
  437. if (deletion.ID == Guid.Empty)
  438. {
  439. Logger.Send(LogType.Error, "", "Empty Deletion ID");
  440. return;
  441. }
  442. var entityType = CoreUtils.Entities.FirstOrDefault(x => x.Name == deletion.HeadTable);
  443. if (entityType is null)
  444. {
  445. Logger.Send(LogType.Error, "", $"Entity {deletion.HeadTable} does not exist");
  446. return;
  447. }
  448. var purgeMethod = typeof(Update_7_06_Class).GetMethod(nameof(PurgeEntityType), BindingFlags.NonPublic | BindingFlags.Static)!;
  449. purgeMethod.MakeGenericMethod(entityType).Invoke(null, new object[] { deletion });
  450. DbFactory.Provider.Purge<Deletion>(deletion);
  451. }
  452. }
  453. private static bool Update_7_06()
  454. {
  455. var deletions = DbFactory.Provider.Query<Deletion>(
  456. new Filter<Deletion>(x => x.Data).IsEqualTo(""));
  457. Logger.Send(LogType.Information, "", "Updating Deletions");
  458. foreach (var deletion in deletions.ToObjects<Deletion>())
  459. {
  460. Update_7_06_Class.Purge(deletion);
  461. }
  462. Logger.Send(LogType.Information, "", "Finished updating Deletions");
  463. return true;
  464. }
  465. }
  466. }