Update_8_58.cs 34 KB

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