Update_8_58.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953
  1. using Comal.Classes;
  2. using InABox.Configuration;
  3. using InABox.Core;
  4. using InABox.Database;
  5. using InABox.DynamicGrid;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Linq.Expressions;
  10. using System.Reflection;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. using InABox.Database.SQLite;
  14. namespace PRS.Shared.Database_Update_Scripts;
  15. internal class Update_8_58 : DatabaseUpdateScript
  16. {
  17. public override VersionNumber Version => new(8, 58);
  18. private static void UpdateTimeSheets(IProvider provider)
  19. {
  20. Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted");
  21. var timeSheets = provider.Query(
  22. Filter<TimeSheet>.Where(x => x.Processed).IsNotEqualTo(DateTime.MinValue)
  23. .And(x => x.Posted).IsEqualTo(DateTime.MinValue),
  24. Columns.None<TimeSheet>()
  25. .Add(x => x.ID)
  26. .Add(x => x.Processed)
  27. .Add(x => x.Posted))
  28. .ToArray<TimeSheet>();
  29. if (timeSheets.Length == 0)
  30. {
  31. Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted: Done");
  32. return;
  33. }
  34. Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted: {timeSheets.Length} items");
  35. Utils.Utils.ProcessInChunks(
  36. timeSheets,
  37. chunk =>
  38. {
  39. foreach (var timeSheet in chunk)
  40. {
  41. timeSheet.Posted = timeSheet.Processed;
  42. }
  43. provider.Save(chunk);
  44. },
  45. 200,
  46. percentage => Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed: {percentage:F2}%"));
  47. }
  48. private static void ConvertLink<T>(IProvider provider, string fromLink, string toLink)
  49. where T : Entity, new()
  50. {
  51. var fromColumn = new Column<T>(fromLink).SubColumn(new Column<IEntityLink>(x => x.ID));
  52. var toColumn = new Column<T>(toLink).SubColumn(new Column<IEntityLink>(x => x.ID));
  53. if (provider is SQLiteProvider p)
  54. {
  55. Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property} -> {typeof(T).Name}.{toColumn.Property}");
  56. var sql =
  57. $"update {typeof(T).Name} set [{toColumn.Property}]=[{fromColumn.Property}] where [{fromColumn.Property}] is not null and [{toColumn.Property}] is null";
  58. p.Update(sql);
  59. return;
  60. }
  61. var items = provider.Query(
  62. Filter<T>.Where<Guid>(fromColumn).IsNotEqualTo(Guid.Empty)
  63. .And<Guid>(toColumn).IsEqualTo(Guid.Empty),
  64. Columns.None<T>()
  65. .Add(x => x.ID)
  66. .Add(toColumn) // The order here matters a lot; since I've made a lot of obsolete links just point directly to the new one, then we have to set the new before the old.
  67. .Add(fromColumn))
  68. .ToArray<T>();
  69. if (items.Length == 0)
  70. {
  71. return;
  72. }
  73. Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property} -> {typeof(T).Name}.{toColumn.Property}: {items.Length} items");
  74. Utils.Utils.ProcessInChunks(
  75. items,
  76. chunk =>
  77. {
  78. foreach (var item in chunk)
  79. {
  80. item.SetObserving(false);
  81. var originalValue = toColumn.PropertyDefinition.Getter()(item);
  82. toColumn.PropertyDefinition.Setter()(
  83. item,
  84. fromColumn.PropertyDefinition.Getter()(item));
  85. item.SetOriginalValue(toColumn.Property, originalValue);
  86. item.SetObserving(true);
  87. }
  88. provider.Save(chunk);
  89. },
  90. 1000,
  91. percentage => Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property}: {percentage:F2}%"));
  92. }
  93. private static void ConvertLink<T, TFromLink, TToLink>(IProvider provider, Expression<Func<T, TFromLink>> fromLink, Expression<Func<T, TToLink>> toLink)
  94. where T : Entity, new()
  95. where TFromLink : IEntityLink
  96. where TToLink : IEntityLink
  97. {
  98. ConvertLink<T>(provider, CoreUtils.GetFullPropertyName(fromLink, "."), CoreUtils.GetFullPropertyName(toLink, "."));
  99. }
  100. private static void ConvertLink<T, TLink>(IProvider provider, Expression<Func<T, TLink>> fromLink, Expression<Func<T, TLink>> toLink)
  101. where T : Entity, new()
  102. where TLink : IEntityLink
  103. {
  104. ConvertLink<T, TLink, TLink>(provider, fromLink, toLink);
  105. }
  106. private static string QuoteString(string str)
  107. {
  108. return $"\"{str.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
  109. }
  110. private static void ConvertQAQuestions(IProvider provider)
  111. {
  112. var qaQuestions = provider.Query(
  113. Filter<QAQuestion>.Where(x => x.Converted).IsEqualTo(false),
  114. Columns.None<QAQuestion>()
  115. .Add(x => x.ID)
  116. .Add(x => x.Converted)
  117. .Add(x => x.Code)
  118. .Add(x => x.Question)
  119. .Add(x => x.Section)
  120. .Add(x => x.Description)
  121. .Add(x => x.Answer)
  122. .Add(x => x.Parameters)
  123. .Add(x => x.Sequence)
  124. .Add(x => x.Form.ID))
  125. .ToObjects<QAQuestion>()
  126. .GroupBy(x => x.Form.ID)
  127. .Select(x => new
  128. {
  129. FormID = x.Key,
  130. Questions = x.OrderBy(x => x.Sequence).ToList()
  131. })
  132. .ToList();
  133. Logger.Send(LogType.Information, "", $"Converting {qaQuestions.Count} QA forms into DigitalForms");
  134. var formSequences = provider.Query(
  135. Filter<DigitalFormVariable>.Where(x => x.Form.ID)
  136. .InList(qaQuestions.ToArray(x => x.FormID)),
  137. Columns.None<DigitalFormVariable>()
  138. .Add(x => x.Form.ID)
  139. .Add(x => x.Sequence))
  140. .ToObjects<DigitalFormVariable>()
  141. .GroupBy(x => x.Form.ID)
  142. .ToDictionary(x => x.Key, x => x.Max(x => x.Sequence));
  143. var variableMappings = new Dictionary<Guid, Dictionary<Guid, string>>();
  144. var variables = new List<DigitalFormVariable>();
  145. var layouts = new List<DigitalFormLayout>();
  146. foreach (var item in qaQuestions)
  147. {
  148. if (formSequences.TryGetValue(item.FormID, out var sequence))
  149. {
  150. sequence++;
  151. }
  152. else
  153. {
  154. sequence = 0;
  155. }
  156. var layout = new DFLayout();
  157. layout.ColumnWidths.Add("Auto");
  158. layout.ColumnWidths.Add("*");
  159. var codes = new HashSet<string>();
  160. string GenerateCode(string code)
  161. {
  162. var originalCode = code;
  163. var i = 1;
  164. while (codes.Contains(code))
  165. {
  166. code = $"{originalCode}{i}";
  167. ++i;
  168. }
  169. return code;
  170. }
  171. var mappings = variableMappings.GetValueOrAdd(item.FormID);
  172. var nButtons = 0;
  173. var i = 1;
  174. foreach (var question in item.Questions)
  175. {
  176. layout.RowHeights.Add("Auto");
  177. var row = layout.RowHeights.Count;
  178. if (question.Answer == QAAnswer.Comment)
  179. {
  180. var label = new DFLayoutLabel { Caption = question.Question, Row = row, Column = 1, ColumnSpan = 3 };
  181. label.Style.HorizontalTextAlignment = DFLayoutAlignment.Middle;
  182. layout.Elements.Add(label);
  183. }
  184. else
  185. {
  186. var rowNum = new DFLayoutLabel { Caption = i.ToString(), Row = row, Column = 1 };
  187. var label = new DFLayoutLabel { Caption = question.Question, Row = row, Column = 2 };
  188. layout.Elements.Add(rowNum);
  189. layout.Elements.Add(label);
  190. var variable = new DigitalFormVariable();
  191. variable.Form.CopyFrom(question.Form);
  192. variable.Sequence = sequence++;
  193. DFLayoutFieldProperties properties;
  194. Type fieldType;
  195. var code = GenerateCode(question.Code.NotWhiteSpaceOr(question.Answer.ToString()));
  196. var parameters = question.ParseParameters();
  197. switch (question.Answer)
  198. {
  199. case QAAnswer.Choice:
  200. {
  201. // ColourExpression
  202. var buttons = parameters["Options"].Split(',').ToArray(x => x.Trim());
  203. var colors = parameters["Colors"].Split(',').ToArray(x => x.Trim());
  204. var defValue = parameters["Default"].Trim();
  205. fieldType = typeof(DFLayoutOptionField);
  206. var optionProperties = new DFLayoutOptionFieldProperties();
  207. properties = optionProperties;
  208. optionProperties.Default = defValue;
  209. optionProperties.OptionType = DFLayoutOptionType.Buttons;
  210. optionProperties.Options = DFLayoutOptionFieldProperties.WriteOptions(buttons);
  211. var colourExpression = "null";
  212. foreach (var (option, colour) in buttons.Zip(colors))
  213. {
  214. colourExpression = $"If([{code}] == {QuoteString(option)}, {QuoteString(colour)}, {colourExpression})";
  215. }
  216. optionProperties.ColourExpression = colourExpression;
  217. nButtons = Math.Max(nButtons, buttons.Length);
  218. }
  219. break;
  220. case QAAnswer.Number:
  221. {
  222. var defValue = parameters["Default"];
  223. fieldType = typeof(DFLayoutDoubleField);
  224. var doubleProperties = new DFLayoutDoubleFieldProperties();
  225. properties = doubleProperties;
  226. doubleProperties.Default = double.TryParse(defValue, out var d) ? d : default;
  227. }
  228. break;
  229. case QAAnswer.Text:
  230. {
  231. var defValue = parameters["Default"];
  232. fieldType = typeof(DFLayoutStringField);
  233. var stringProperties = new DFLayoutStringFieldProperties();
  234. properties = stringProperties;
  235. stringProperties.Default = defValue;
  236. }
  237. break;
  238. case QAAnswer.Combo:
  239. {
  240. var buttons = parameters["Options"].Split(',');
  241. var defValue = parameters["Default"];
  242. fieldType = typeof(DFLayoutOptionField);
  243. var optionProperties = new DFLayoutOptionFieldProperties();
  244. properties = optionProperties;
  245. optionProperties.Default = defValue;
  246. optionProperties.OptionType = DFLayoutOptionType.Combo;
  247. optionProperties.Options = DFLayoutOptionFieldProperties.WriteOptions(buttons);
  248. }
  249. break;
  250. default:
  251. throw new Exception("Impossible");
  252. }
  253. properties.Code = code;
  254. properties.Description = question.Description.NotWhiteSpaceOr(question.Question);
  255. properties.Required = parameters.GetValueOrDefault("Default").IsNullOrWhiteSpace();
  256. variable.SaveProperties(fieldType, properties);
  257. mappings.Add(question.ID, variable.Code);
  258. codes.Add(variable.Code);
  259. var field = (Activator.CreateInstance(variable.FieldType()) as DFLayoutField)!;
  260. field.Name = variable.Code;
  261. field.Row = row;
  262. field.Column = 3;
  263. layout.Elements.Add(field);
  264. variables.Add(variable);
  265. ++i;
  266. }
  267. question.Converted = true;
  268. }
  269. layout.ColumnWidths.Add(Math.Max(150, nButtons * 80).ToString());
  270. var dfLayout = new DigitalFormLayout();
  271. dfLayout.Form.ID = item.FormID;
  272. dfLayout.Layout = layout.SaveLayout();
  273. dfLayout.Description = "Generated from QA form";
  274. dfLayout.Type = DFLayoutType.Desktop;
  275. dfLayout.Active = true;
  276. layouts.Add(dfLayout);
  277. }
  278. provider.Save(variables);
  279. provider.Save(layouts);
  280. provider.Save(qaQuestions.SelectMany(x => x.Questions));
  281. FormUpdater.UpdateAllForms(
  282. (form, variables) => false,
  283. (formType, instance, form, variables) =>
  284. {
  285. if (!variableMappings.TryGetValue(form.ID, out var mappings)) return false;
  286. var values = DigitalForm.DeserializeFormSaveData(instance) ?? new();
  287. var items = values.ToLoadStorage().Items().ToArray();
  288. foreach (var (key, value) in items)
  289. {
  290. if (!Guid.TryParse(key, out var id)) continue;
  291. if (!mappings.TryGetValue(id, out var code)) continue;
  292. values.AddValue(code, value?.ToString()?.Trim());
  293. }
  294. DigitalForm.SerializeFormData(instance, values);
  295. return true;
  296. },
  297. filter: Filter<DigitalForm>.Where(x => x.ID).InList(qaQuestions.ToArray(x => x.FormID)));
  298. }
  299. private interface IExtraMap
  300. {
  301. public Type TFrom { get; }
  302. public Type TTo { get; }
  303. IProperty From { get; }
  304. IProperty To { get; }
  305. Func<object?, object?>? Map { get; }
  306. }
  307. private class ExtraMap<TFrom, TTo> : IExtraMap
  308. {
  309. Type IExtraMap.TFrom => typeof(TFrom);
  310. Type IExtraMap.TTo => typeof(TTo);
  311. public IProperty From { get; }
  312. public IProperty To { get; }
  313. public Func<object?, object?>? Map { get; init; } = null;
  314. public ExtraMap(Expression<Func<TFrom, object?>> from, Expression<Func<TTo, object?>> to)
  315. {
  316. From = DatabaseSchema.PropertyStrict(from);
  317. To = DatabaseSchema.PropertyStrict(to);
  318. }
  319. public ExtraMap(IProperty from, IProperty to)
  320. {
  321. From = from;
  322. To = to;
  323. }
  324. public static ExtraMap<TFrom, TTo> New<TFromValue, TToValue>(
  325. Expression<Func<TFrom, TFromValue>> from,
  326. Expression<Func<TTo, TToValue>> to,
  327. Func<TFromValue, TToValue> map)
  328. {
  329. return new(
  330. DatabaseSchema.PropertyStrict(from),
  331. DatabaseSchema.PropertyStrict(to))
  332. {
  333. Map = x => x is TFromValue tFrom ? map(tFrom) : default
  334. };
  335. }
  336. }
  337. private static void RenameTable<TFrom, TTo>(IProvider provider,
  338. List<ExtraMap<TFrom, TTo>>? extraMaps = null)
  339. where TFrom : Entity, new()
  340. where TTo : Entity, new()
  341. {
  342. var currentMaps = new HashSet<string>();
  343. var maps = new List<(IProperty from, IProperty to, Func<object?, object?>? map)>();
  344. foreach (var map in extraMaps ?? [])
  345. {
  346. currentMaps.Add(map.From.Name);
  347. maps.Add((map.From, map.To, map.Map));
  348. }
  349. foreach (var fromProperty in DatabaseSchema.LocalProperties(typeof(TFrom)))
  350. {
  351. if (currentMaps.Contains(fromProperty.Name)) continue;
  352. if (DatabaseSchema.Property(typeof(TTo), fromProperty.Name) is IProperty toProperty)
  353. {
  354. if (fromProperty.PropertyType != toProperty.PropertyType)
  355. {
  356. throw new Exception($"Cannot migrate {typeof(TFrom).Name}.{fromProperty.Name} -> {typeof(TTo).Name}.{toProperty.Name}: type mismatch");
  357. }
  358. maps.Add((fromProperty, toProperty, null));
  359. }
  360. else
  361. {
  362. }
  363. }
  364. var items = provider.Query<TFrom>(
  365. Filter<TFrom>.Where(x => x.ID).NotInQuery(Filter.All<TTo>(), x => x.ID),
  366. Columns.None<TFrom>()
  367. .Add(x => x.ID)
  368. .Add(maps.Select(x => new Column<TFrom>(x.from))))
  369. .ToArray<TFrom>();
  370. if (items.Length == 0) return;
  371. Logger.Send(LogType.Information, "", $"Migrating {typeof(TFrom).Name} -> {typeof(TTo).Name}: {items.Length} items");
  372. Utils.Utils.ProcessInChunks(
  373. items,
  374. chunk =>
  375. {
  376. var newItems = new List<TTo>();
  377. foreach (var item in chunk)
  378. {
  379. var newItem = new TTo();
  380. newItem.SetObserving(false);
  381. newItem.ID = item.ID;
  382. foreach (var (from, to, map) in maps)
  383. {
  384. if (map is not null)
  385. {
  386. to.Setter()(newItem, map(from.Getter()(item)));
  387. }
  388. else
  389. {
  390. to.Setter()(newItem, from.Getter()(item));
  391. }
  392. }
  393. newItem.SetObserving(true);
  394. newItems.Add(newItem);
  395. }
  396. provider.Save(newItems);
  397. },
  398. 1000,
  399. percentage => Logger.Send(LogType.Information, "", $"Migrating {typeof(TFrom).Name}: {percentage:F2}%"));
  400. ConvertColumnsTag(provider, typeof(TFrom).Name, typeof(TTo).Name);
  401. UpdateAutoSecurityTokens<TFrom, TTo>(provider);
  402. }
  403. private static void ConvertLinks(IProvider provider)
  404. {
  405. ConvertLink<Assignment, JobLink>(provider, x => x.JobLink, x => x.Job);
  406. ConvertLink<EquipmentAssignment, JobLink>(provider, x => x.JobLink, x => x.Job);
  407. ConvertLink<Kanban, JobLink>(provider, x => x.JobLink, x => x.Job);
  408. ConvertLink<Requisition, JobLink>(provider, x => x.JobLink, x => x.Job);
  409. ConvertLink<RequisitionItem, JobLink>(provider, x => x.JobLink, x => x.Job);
  410. ConvertLink<Setout, JobLink>(provider, x => x.JobLink, x => x.Job);
  411. ConvertLink<JobBillOfMaterialsActivity, JobLink>(provider, x => x.JobLink, x => x.Job);
  412. ConvertLink<ManufacturingSection, QAFormLink, DigitalFormLink>(provider, x => x.QAForm, x => x.DigitalForm);
  413. ConvertLink<ManufacturingTemplateStage, QAFormLink, DigitalFormLink>(provider, x => x.QAForm, x => x.DigitalForm);
  414. var method = typeof(Update_8_58).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
  415. .Where(x => x.Name == nameof(ConvertLink)
  416. && x.IsGenericMethod
  417. && x.GetGenericArguments().Length == 1)
  418. .First();
  419. foreach (var entity in DbFactory.ProviderFactory.Types.Where(x => x.HasInterface(typeof(IEntityDocument))))
  420. {
  421. var entityMethod = method.MakeGenericMethod(entity);
  422. var entityFromProp = DatabaseSchema.PropertyStrict<IEntityDocument>(entity, x => x.EntityLink);
  423. var entityToProp = DatabaseSchema.PropertyStrict<IEntityDocument>(entity, x => x.Entity);
  424. entityMethod.Invoke(null, [provider, entityFromProp.Name, entityToProp.Name]);
  425. var documentFromProp = DatabaseSchema.PropertyStrict<IEntityDocument>(entity, x => x.DocumentLink);
  426. var documentToProp = DatabaseSchema.PropertyStrict<IEntityDocument>(entity, x => x.Document);
  427. entityMethod.Invoke(null, [provider, documentFromProp.Name, documentToProp.Name]);
  428. }
  429. foreach (var entity in DbFactory.ProviderFactory.Types)
  430. {
  431. MethodInfo? entityMethod = null;
  432. foreach (var property in DatabaseSchema.LocalProperties(entity))
  433. {
  434. if (property.Parent is null
  435. || property.Parent.PropertyType != typeof(LocalityLink)
  436. || property.Parent.Parent is null
  437. || property.Parent.Parent.PropertyType != typeof(Address)) continue;
  438. var addressProp = property.Parent.Parent;
  439. entityMethod ??= method.MakeGenericMethod(entity);
  440. var fromProp = DatabaseSchema.PropertyStrict(entity, addressProp.Name + "." + nameof(Address.LocalityLink));
  441. var toProp = DatabaseSchema.PropertyStrict(entity, addressProp.Name + "." + nameof(Address.Locality));
  442. entityMethod.Invoke(null, [provider, fromProp.Name, toProp.Name]);
  443. }
  444. }
  445. ConvertLink<Schedule, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  446. ConvertLink<Schedule, EmployeeLink>(provider, x => x.ManagerLink, x => x.Manager);
  447. ConvertLink<Assignment, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  448. ConvertLink<Assignment, AssignmentActivityLink>(provider, x => x.ActivityLink, x => x.Activity);
  449. ConvertLink<Assignment, LeaveRequestLink>(provider, x => x.LeaveRequestLink, x => x.LeaveRequest);
  450. ConvertLink<Bill, SupplierLink>(provider, x => x.SupplierLink, x => x.Supplier);
  451. ConvertLink<BillLine, BillLink>(provider, x => x.BillLink, x => x.Bill);
  452. ConvertLink<DeliveryItem, JobLink>(provider, x => x.JobLink, x => x.Job);
  453. ConvertLink<DeliveryItem, ManufacturingPacketLink>(provider, x => x.ManufacturingPacketLink, x => x.ManufacturingPacket);
  454. ConvertLink<DeliveryItem, RequisitionLink, PickingListLink>(provider, x => x.RequisitionLink, x => x.PickingList);
  455. ConvertLink<DeliveryItem, SetoutLink>(provider, x => x.SetoutLink, x => x.Setout);
  456. ConvertLink<DeliveryItem, ShipmentLink>(provider, x => x.ShipmentLink, x => x.Shipment);
  457. ConvertLink<Employee, UserLink>(provider, x => x.UserLink, x => x.User);
  458. ConvertLink<Employee, OvertimeRuleLink>(provider, x => x.OvertimeRuleLink, x => x.OvertimeRule);
  459. ConvertLink<EmployeeRole, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  460. ConvertLink<EmployeeRole, RoleLink>(provider, x => x.RoleLink, x => x.Role);
  461. ConvertLink<Equipment, EquipmentGroupLink>(provider, x => x.GroupLink, x => x.Group);
  462. ConvertLink<Equipment, GPSTrackerLink>(provider, x => x.TrackerLink, x => x.Tracker);
  463. ConvertLink<Invoice, JobLink>(provider, x => x.JobLink, x => x.Job);
  464. ConvertLink<Invoice, CustomerLink>(provider, x => x.CustomerLink, x => x.Customer);
  465. ConvertLink<InvoiceLine, InvoiceLink>(provider, x => x.InvoiceLink, x => x.Invoice);
  466. ConvertLink<Job, ScheduleLink>(provider, x => x.ScheduleLink, x => x.Schedule);
  467. ConvertLink<JobActivity, AssignmentActivityLink>(provider, x => x.ActivityLink, x => x.Activity);
  468. ConvertLink<JobBillOfMaterialsActivity, AssignmentActivityLink>(provider, x => x.ActivityLink, x => x.Activity);
  469. ConvertLink<JobEmployee, JobLink>(provider, x => x.JobLink, x => x.Job);
  470. ConvertLink<JobEmployee, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  471. ConvertLink<JobEquipment, JobLink>(provider, x => x.JobLink, x => x.Job);
  472. ConvertLink<JobEquipment, EquipmentLink>(provider, x => x.EquipmentLink, x => x.Equipment);
  473. ConvertLink<JobTracker, JobLink>(provider, x => x.JobLink, x => x.Job);
  474. ConvertLink<JobTracker, GPSTrackerLink>(provider, x => x.TrackerLink, x => x.Tracker);
  475. ConvertLink<JobITP, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  476. ConvertLink<Kanban, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  477. ConvertLink<Kanban, EmployeeLink>(provider, x => x.ManagerLink, x => x.Manager);
  478. ConvertLink<Kanban, ScheduleLink>(provider, x => x.ScheduleLink, x => x.Schedule);
  479. ConvertLink<LeaveRequest, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  480. ConvertLink<ManufacturingPacket, SetoutLink>(provider, x => x.SetoutLink, x => x.Setout);
  481. ConvertLink<ManufacturingPacket, ManufacturingTemplateLink>(provider, x => x.ManufacturingTemplateLink, x => x.ManufacturingTemplate);
  482. ConvertLink<ManufacturingPacket, ManufacturingPacketStageLink>(provider, x => x.StageLink, x => x.Stage);
  483. ConvertLink<ManufacturingPacketStage, ManufacturingSectionLink>(provider, x => x.ManufacturingSectionLink, x => x.ManufacturingSection);
  484. ConvertLink<ManufacturingPacketComponent, RequisitionLink, PickingListLink>(provider, x => x.Requisition, x => x.PickingList);
  485. ConvertLink<BillPayment, BillLink>(provider, x => x.BillLink, x => x.Bill);
  486. ConvertLink<BillPayment, PaymentLink>(provider, x => x.PaymentLink, x => x.Payment);
  487. ConvertLink<Payment, SupplierLink>(provider, x => x.SupplierLink, x => x.Supplier);
  488. ConvertLink<Payment, PaymentTypeLink>(provider, x => x.PaymentTypeLink, x => x.PaymentType);
  489. ConvertLink<PurchaseOrder, SupplierLink>(provider, x => x.SupplierLink, x => x.Supplier);
  490. ConvertLink<PurchaseOrderItem, PurchaseOrderLink>(provider, x => x.PurchaseOrderLink, x => x.PurchaseOrder);
  491. ConvertLink<InvoiceReceipt, InvoiceLink>(provider, x => x.InvoiceLink, x => x.Invoice);
  492. ConvertLink<InvoiceReceipt, ReceiptLink>(provider, x => x.ReceiptLink, x => x.Receipt);
  493. ConvertLink<Receipt, CustomerLink>(provider, x => x.CustomerLink, x => x.Customer);
  494. ConvertLink<Receipt, ReceiptTypeLink>(provider, x => x.ReceiptTypeLink, x => x.ReceiptType);
  495. ConvertLink<RequisitionItem, RequisitionLink>(provider, x => x.RequisitionLink, x => x.Requisition);
  496. ConvertLink<Shipment, GPSTrackerLink>(provider, x => x.TrackerLink, x => x.Tracker);
  497. ConvertLink<SupplierProduct, SupplierLink>(provider, x => x.SupplierLink, x => x.Supplier);
  498. ConvertLink<StockMovementBatch, RequisitionLink, PickingListLink>(provider, x => x.Requisition, x => x.PickingList);
  499. ConvertLink<EmployeeTeam, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  500. ConvertLink<EmployeeTeam, TeamLink>(provider, x => x.TeamLink, x => x.Team);
  501. ConvertLink<TimeSheet, EmployeeLink>(provider, x => x.EmployeeLink, x => x.Employee);
  502. ConvertLink<TimeSheet, JobLink>(provider, x => x.JobLink, x => x.Job);
  503. ConvertLink<TimeSheet, TimeSheetActivityLink>(provider, x => x.ActivityLink, x => x.Activity);
  504. ConvertLink<TimeSheet, LeaveRequestLink>(provider, x => x.LeaveRequestLink, x => x.LeaveRequest);
  505. ConvertLink<TimeSheet, StandardLeaveLink>(provider, x => x.StandardLeaveLink, x => x.StandardLeave);
  506. RenameTable<Requisition, PickingList>(provider);
  507. RenameTable<RequisitionItem, PickingListItem>(provider,
  508. [
  509. new(x => x.Requisition.ID, x => x.PickingList.ID)
  510. ]);
  511. RenameTable<RequisitionDestination, PickingListDestination>(provider);
  512. RenameTable<RequisitionDocument, PickingListDocument>(provider);
  513. RenameTable<RequisitionKanban, PickingListKanban>(provider);
  514. }
  515. private static Dictionary<Type, Dictionary<string, IExtraMap>> _filterMaps = [];
  516. private static IExtraMap[] GetFilterMaps()
  517. {
  518. return new IExtraMap[]
  519. {
  520. new ExtraMap<Kanban, Kanban>(DatabaseSchema.PropertyStrict(typeof(Kanban), "Category"), DatabaseSchema.PropertyStrict<Kanban>(x => x.Status))
  521. {
  522. Map = x =>
  523. {
  524. if(x is string category)
  525. {
  526. if (String.IsNullOrWhiteSpace(category) || category.Equals("Open"))
  527. return KanbanStatus.Open;
  528. if(category.Equals("In Progress"))
  529. return KanbanStatus.InProgress;
  530. if (category.Equals("Waiting"))
  531. return KanbanStatus.Waiting;
  532. if (category.Equals("Complete"))
  533. return KanbanStatus.Complete;
  534. return KanbanStatus.Open;
  535. }
  536. else
  537. {
  538. return KanbanStatus.Open;
  539. }
  540. }
  541. }
  542. };
  543. }
  544. private static bool CheckFilter(IFilter filter, Dictionary<string, IExtraMap>? maps)
  545. {
  546. var changed = false;
  547. if(maps is not null && maps.TryGetValue(filter.Property, out var map))
  548. {
  549. filter.Property = map.To.Name;
  550. var value = map.Map?.Invoke(filter.Value);
  551. if(value is null)
  552. {
  553. filter.Value = null;
  554. changed = true;
  555. }
  556. else if (filter.Type.IsAssignableFrom(value.GetType()))
  557. {
  558. filter.Value = value;
  559. changed = true;
  560. }
  561. }
  562. if (!changed)
  563. {
  564. var columnName = ConvertColumnName(filter.Type, filter.Property);
  565. if(columnName is not null)
  566. {
  567. filter.Property = columnName;
  568. changed = true;
  569. }
  570. }
  571. foreach(var and in filter.Ands)
  572. {
  573. changed = CheckFilter(and, maps) || changed;
  574. }
  575. foreach(var or in filter.Ors)
  576. {
  577. changed = CheckFilter(or, maps) || changed;
  578. }
  579. return changed;
  580. }
  581. private static void UpdateFilters<T>(IProvider provider)
  582. where T : Entity, IDatabaseStoredSettings, new()
  583. {
  584. var settings = provider.Query(
  585. Filter<T>.Where(x => x.Section).IsEqualTo(nameof(CoreFilterDefinitions)),
  586. Columns.None<T>()
  587. .Add(x => x.ID)
  588. .Add(x => x.Key)
  589. .Add(x => x.Contents))
  590. .ToArray<T>();
  591. var changedSettings = new List<T>();
  592. foreach(var setting in settings)
  593. {
  594. if (setting.Key.IsNullOrWhiteSpace()) continue;
  595. var entityName = setting.Key.Split('.')[^1];
  596. var entity = CoreUtils.Entities.Where(x => x.Name == entityName
  597. && x.IsSubclassOf(typeof(Entity)))
  598. .FirstOrDefault();
  599. var filters = Serialization.Deserialize<CoreFilterDefinitions>(setting.Contents);
  600. if(filters is not null)
  601. {
  602. var changed = false;
  603. foreach(var filter in filters)
  604. {
  605. var deserialiseEntity = entity is not null ? typeof(Filter<>).MakeGenericType(entity) : typeof(IFilter);
  606. IFilter? deserialised = null;
  607. try
  608. {
  609. deserialised = (Serialization.Deserialize(deserialiseEntity, filter.Filter, strict: true) as IFilter)!;
  610. if(deserialised is null)
  611. {
  612. continue;
  613. }
  614. }
  615. catch (Exception e)
  616. {
  617. var dict = Serialization.Deserialize<Dictionary<string, object?>>(filter.Filter) ?? [];
  618. if (dict.TryGetValue("Expression", out var expression) && expression is string str)
  619. {
  620. try
  621. {
  622. var exp = CoreUtils.StringToExpression(str);
  623. if(exp is MemberExpression memExp)
  624. {
  625. deserialiseEntity = memExp.Expression?.Type;
  626. if(deserialiseEntity is not null)
  627. {
  628. deserialiseEntity = typeof(Filter<>).MakeGenericType(deserialiseEntity);
  629. deserialised = (Serialization.Deserialize(deserialiseEntity, filter.Filter, strict: true) as IFilter)!;
  630. }
  631. }
  632. }
  633. catch(Exception)
  634. {
  635. }
  636. }
  637. if(deserialised is null)
  638. {
  639. Logger.Send(LogType.Error, "", $"Error in deserialising filter '{filter.Name}' of {setting.Key}: {CoreUtils.FormatException(e)}");
  640. continue;
  641. }
  642. }
  643. var type = deserialised.GetType().GetSuperclassDefinition(typeof(Filter<>))!.GenericTypeArguments[0];
  644. if (CheckFilter(deserialised, _filterMaps.GetValueOrDefault(type)))
  645. {
  646. filter.Filter = Serialization.Serialize(deserialised);
  647. changed = true;
  648. }
  649. }
  650. if (changed)
  651. {
  652. setting.Contents = Serialization.Serialize(filters);
  653. changedSettings.Add(setting);
  654. }
  655. }
  656. }
  657. provider.Save(changedSettings);
  658. }
  659. private static void UpdateFilters(IProvider provider)
  660. {
  661. _filterMaps = GetFilterMaps()
  662. .GroupBy(x => x.TFrom)
  663. .ToDictionary(x => x.Key, x => x.ToDictionary(x => x.From.Name));
  664. UpdateFilters<GlobalSettings>(provider);
  665. UpdateFilters<UserSettings>(provider);
  666. }
  667. private static string? ConvertColumnName(Type entity, string originalColumnName)
  668. {
  669. var i = 0;
  670. var changed = false;
  671. while(i < originalColumnName.Length)
  672. {
  673. var index = originalColumnName.IndexOf("Link.", i);
  674. if (index == -1) break;
  675. var columnName = originalColumnName[0..index];
  676. if(DatabaseSchema.Property(entity, columnName) is IProperty property)
  677. {
  678. originalColumnName = $"{property.Name}.{originalColumnName[(index + 5)..]}";
  679. changed = true;
  680. }
  681. else
  682. {
  683. i = index + 5;
  684. }
  685. }
  686. return changed ? originalColumnName : null;
  687. }
  688. private static void UpdateColumns<T>(IProvider provider)
  689. where T : Entity, IDatabaseStoredSettings, new()
  690. {
  691. var settings = provider.Query(
  692. Filter<T>.Where(x => x.Section).IsEqualTo(nameof(DynamicGridColumns)),
  693. Columns.None<T>()
  694. .Add(x => x.ID)
  695. .Add(x => x.Key)
  696. .Add(x => x.Contents))
  697. .ToArray<T>();
  698. var changedSettings = new List<T>();
  699. foreach(var setting in settings)
  700. {
  701. if (setting.Key.IsNullOrWhiteSpace()) continue;
  702. var entityName = setting.Key.Split('.')[^1];
  703. var entity = CoreUtils.Entities.Where(x => x.Name == entityName
  704. && x.IsSubclassOf(typeof(Entity)))
  705. .FirstOrDefault();
  706. if (entity is null) continue;
  707. var columns = Serialization.Deserialize<DynamicGridColumns>(setting.Contents);
  708. if(columns is not null)
  709. {
  710. var changed = false;
  711. foreach(var column in columns)
  712. {
  713. var columnName = ConvertColumnName(entity, column.ColumnName);
  714. if(columnName is not null)
  715. {
  716. column.ColumnName = columnName;
  717. changed = true;
  718. }
  719. }
  720. if (changed)
  721. {
  722. setting.Contents = Serialization.Serialize(columns);
  723. changedSettings.Add(setting);
  724. }
  725. }
  726. }
  727. provider.Save(changedSettings);
  728. }
  729. private static void UpdateColumns(IProvider provider)
  730. {
  731. UpdateColumns<GlobalSettings>(provider);
  732. UpdateColumns<UserSettings>(provider);
  733. }
  734. private static void UpdateSecurityToken<T>(IProvider provider, string oldDescriptor, string newDescriptor)
  735. where T : Entity, ISecurityToken, new()
  736. {
  737. var tokens = provider.Query(
  738. Filter<T>.Where(x => x.Descriptor).IsEqualTo(oldDescriptor),
  739. Columns.None<T>()
  740. .Add(x => x.ID)
  741. .Add(x => x.Descriptor))
  742. .ToArray<T>();
  743. foreach(var token in tokens)
  744. {
  745. token.Descriptor = newDescriptor;
  746. }
  747. provider.Save(tokens);
  748. }
  749. private static void UpdateSecurityToken(IProvider provider, string oldDescriptor, string newDescriptor)
  750. {
  751. UpdateSecurityToken<GlobalSecurityToken>(provider, oldDescriptor, newDescriptor);
  752. UpdateSecurityToken<UserSecurityToken>(provider, oldDescriptor, newDescriptor);
  753. UpdateSecurityToken<SecurityToken>(provider, oldDescriptor, newDescriptor);
  754. }
  755. private static void UpdateAutoSecurityToken<TFrom, TTo>(IProvider provider, Type token)
  756. {
  757. var toDescriptor = (Activator.CreateInstance(token.MakeGenericType(typeof(TTo))) as IAutoSecurityDescriptor)!;
  758. var overriden = Security.SecurityDescriptorOverride(toDescriptor);
  759. if (overriden != toDescriptor) return; // No point in updating these if the token has been overriden.
  760. var fromDescriptor = (Activator.CreateInstance(token.MakeGenericType(typeof(TFrom))) as IAutoSecurityDescriptor)!;
  761. UpdateSecurityToken(provider, Security.SecurityDescriptorOverride(fromDescriptor).Code, toDescriptor.Code);
  762. }
  763. private static void UpdateAutoSecurityTokens<TFrom, TTo>(IProvider provider)
  764. {
  765. var list = CoreUtils.Entities.Where(
  766. x => x.HasInterface(typeof(IAutoSecurityDescriptor)))
  767. .ToArray();
  768. foreach(var T in CoreUtils.Entities.Where(
  769. x => x.HasInterface(typeof(IAutoSecurityDescriptor))
  770. && x.IsGenericType && x.GetGenericArguments().Length == 1))
  771. {
  772. UpdateAutoSecurityToken<TFrom, TTo>(provider, T);
  773. }
  774. }
  775. private static void UpdateSecurityTokens(IProvider provider)
  776. {
  777. UpdateSecurityToken(provider, "CanViewRequisitionsDock", nameof(CanViewPickingListDock));
  778. UpdateSecurityToken(provider, "CanSkipRequisitionPhotos", nameof(CanSkipPickingListPhotos));
  779. UpdateSecurityToken(provider, "CanUpdateRequisitionStockMovements", nameof(CanUpdatePickingListStockMovements));
  780. UpdateSecurityToken(provider, "CanArchiveRequisitions", nameof(CanArchivePickingLists));
  781. }
  782. private static void ConvertSettings<T>(IProvider provider, string fromSection, string toSection)
  783. where T : Entity, IDatabaseStoredSettings, new()
  784. {
  785. var settings = provider.Query(
  786. Filter<T>.Where(x => x.Section).IsEqualTo(fromSection),
  787. Columns.None<T>()
  788. .Add(x => x.ID)
  789. .Add(x => x.Section))
  790. .ToArray<T>();
  791. foreach(var setting in settings)
  792. {
  793. setting.Section = toSection;
  794. }
  795. provider.Save(settings);
  796. }
  797. private static void ConvertSettingsKey<TSettings, T>(IProvider provider, string fromKey, string toKey)
  798. where TSettings : Entity, IDatabaseStoredSettings, new()
  799. {
  800. var settings = provider.Query(
  801. Filter<TSettings>.Where(x => x.Section).IsEqualTo(typeof(T).Name)
  802. .And(x => x.Key).IsEqualTo(fromKey),
  803. Columns.None<TSettings>()
  804. .Add(x => x.ID)
  805. .Add(x => x.Key))
  806. .ToArray<TSettings>();
  807. foreach(var setting in settings)
  808. {
  809. setting.Key = toKey;
  810. }
  811. provider.Save(settings);
  812. }
  813. private static void ConvertSettingsKey<T>(IProvider provider, string fromKey, string toKey)
  814. {
  815. ConvertSettingsKey<GlobalSettings, T>(provider, fromKey, toKey);
  816. ConvertSettingsKey<UserSettings, T>(provider, fromKey, toKey);
  817. }
  818. private static void ConvertColumnsTag(IProvider provider, string fromTag, string toTag)
  819. {
  820. ConvertSettingsKey<DynamicGridColumns>(provider, fromTag, toTag);
  821. ConvertSettingsKey<DynamicGridColumns>(provider, $"{fromTag}:DirectEdit", $"{toTag}:DirectEdit");
  822. }
  823. public override bool Update()
  824. {
  825. var provider = DbFactory.NewProvider(Logger.Main);
  826. UpdateTimeSheets(provider);
  827. ConvertLinks(provider);
  828. UpdateFilters(provider);
  829. UpdateColumns(provider);
  830. UpdateSecurityTokens(provider);
  831. ConvertSettings<UserSettings>(provider, "RequisitionSettings", "PickingListSettings");
  832. ConvertColumnsTag(provider, "PickingList.DeliveryRequi", "PickingList.DeliveryPickingList");
  833. ConvertColumnsTag(provider, "RequisitionItem.RequisitionItem_Logistics", "PickingListItem.PickingListItem_Logistics");
  834. ConvertQAQuestions(provider);
  835. return true;
  836. }
  837. }