Update_8_58.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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. namespace PRS.Shared.Database_Update_Scripts;
  11. internal class Update_8_58 : DatabaseUpdateScript
  12. {
  13. public override VersionNumber Version => new(8, 58);
  14. private static void UpdateTimeSheets(IProvider provider)
  15. {
  16. Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted");
  17. var timeSheets = provider.Query(
  18. Filter<TimeSheet>.Where(x => x.Processed).IsNotEqualTo(DateTime.MinValue)
  19. .And(x => x.Posted).IsEqualTo(DateTime.MinValue),
  20. Columns.None<TimeSheet>()
  21. .Add(x => x.ID)
  22. .Add(x => x.Processed)
  23. .Add(x => x.Posted))
  24. .ToArray<TimeSheet>();
  25. if(timeSheets.Length == 0)
  26. {
  27. Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted: Done");
  28. return;
  29. }
  30. Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed -> TimeSheet.Posted: {timeSheets.Length} items");
  31. Utils.Utils.ProcessInChunks(
  32. timeSheets,
  33. chunk =>
  34. {
  35. foreach (var timeSheet in chunk)
  36. {
  37. timeSheet.Posted = timeSheet.Processed;
  38. }
  39. provider.Save(chunk);
  40. },
  41. 200,
  42. percentage => Logger.Send(LogType.Information, "", $"Migrating TimeSheet.Processed: {percentage:F2}%"));
  43. }
  44. private static void ConvertLink<T, TFromLink, TToLink>(IProvider provider, Expression<Func<T, TFromLink>> fromLink, Expression<Func<T, TToLink>> toLink)
  45. where T : Entity, new()
  46. where TFromLink : IEntityLink
  47. where TToLink : IEntityLink
  48. {
  49. var fromColumn = Column<T>.SubColumn(fromLink, new Column<TFromLink>(x => x.ID));
  50. var toColumn = Column<T>.SubColumn(toLink, new Column<TToLink>(x => x.ID));
  51. Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property} -> {typeof(T).Name}.{toColumn.Property}");
  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(fromColumn)
  58. .Add(toColumn))
  59. .ToArray<T>();
  60. if(items.Length == 0)
  61. {
  62. Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property} -> {typeof(T).Name}.{toColumn.Property}: Done");
  63. return;
  64. }
  65. Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property} -> {typeof(T).Name}.{toColumn.Property}: {items.Length} items");
  66. Utils.Utils.ProcessInChunks(
  67. items,
  68. chunk =>
  69. {
  70. foreach (var item in chunk)
  71. {
  72. toColumn.PropertyDefinition.Setter()(
  73. item,
  74. fromColumn.PropertyDefinition.Getter()(item));
  75. }
  76. provider.Save(chunk);
  77. },
  78. 200,
  79. percentage => Logger.Send(LogType.Information, "", $"Migrating {typeof(T).Name}.{fromColumn.Property}: {percentage:F2}%"));
  80. }
  81. private static void ConvertLink<T, TLink>(IProvider provider, Expression<Func<T, TLink>> fromLink, Expression<Func<T, TLink>> toLink)
  82. where T : Entity, new()
  83. where TLink : IEntityLink
  84. {
  85. ConvertLink<T, TLink, TLink>(provider, fromLink, toLink);
  86. }
  87. private static string QuoteString(string str)
  88. {
  89. return $"\"{str.Replace("\\", "\\\\").Replace("\"", "\\\"")}\"";
  90. }
  91. private static void ConvertQAQuestions(IProvider provider)
  92. {
  93. var qaQuestions = provider.Query(
  94. Filter<QAQuestion>.Where(x => x.Converted).IsEqualTo(false),
  95. Columns.None<QAQuestion>()
  96. .Add(x => x.ID)
  97. .Add(x => x.Converted)
  98. .Add(x => x.Code)
  99. .Add(x => x.Question)
  100. .Add(x => x.Section)
  101. .Add(x => x.Description)
  102. .Add(x => x.Answer)
  103. .Add(x => x.Parameters)
  104. .Add(x => x.Sequence)
  105. .Add(x => x.Form.ID))
  106. .ToObjects<QAQuestion>()
  107. .GroupBy(x => x.Form.ID)
  108. .Select(x => new
  109. {
  110. FormID = x.Key,
  111. Questions = x.OrderBy(x => x.Sequence).ToList()
  112. })
  113. .ToList();
  114. Logger.Send(LogType.Information, "", $"Converting {qaQuestions.Count} QA forms into DigitalForms");
  115. var formSequences = provider.Query(
  116. Filter<DigitalFormVariable>.Where(x => x.Form.ID)
  117. .InList(qaQuestions.ToArray(x => x.FormID)),
  118. Columns.None<DigitalFormVariable>()
  119. .Add(x => x.Form.ID)
  120. .Add(x => x.Sequence))
  121. .ToObjects<DigitalFormVariable>()
  122. .GroupBy(x => x.Form.ID)
  123. .ToDictionary(x => x.Key, x => x.Max(x => x.Sequence));
  124. var variableMappings = new Dictionary<Guid, Dictionary<Guid, string>>();
  125. var variables = new List<DigitalFormVariable>();
  126. var layouts = new List<DigitalFormLayout>();
  127. foreach(var item in qaQuestions)
  128. {
  129. if(formSequences.TryGetValue(item.FormID, out var sequence))
  130. {
  131. sequence++;
  132. }
  133. else
  134. {
  135. sequence = 0;
  136. }
  137. var layout = new DFLayout();
  138. layout.ColumnWidths.Add("Auto");
  139. layout.ColumnWidths.Add("*");
  140. var codes = new HashSet<string>();
  141. string GenerateCode(string code)
  142. {
  143. var originalCode = code;
  144. var i = 1;
  145. while (codes.Contains(code))
  146. {
  147. code = $"{originalCode}{i}";
  148. ++i;
  149. }
  150. return code;
  151. }
  152. var mappings = variableMappings.GetValueOrAdd(item.FormID);
  153. var nButtons = 0;
  154. var i = 1;
  155. foreach(var question in item.Questions)
  156. {
  157. layout.RowHeights.Add("Auto");
  158. var row = layout.RowHeights.Count;
  159. if(question.Answer == QAAnswer.Comment)
  160. {
  161. var label = new DFLayoutLabel { Caption = question.Question, Row = row, Column = 1, ColumnSpan = 3 };
  162. label.Style.HorizontalTextAlignment = DFLayoutAlignment.Middle;
  163. layout.Elements.Add(label);
  164. }
  165. else
  166. {
  167. var rowNum = new DFLayoutLabel { Caption = i.ToString(), Row = row, Column = 1 };
  168. var label = new DFLayoutLabel { Caption = question.Question, Row = row, Column = 2 };
  169. layout.Elements.Add(rowNum);
  170. layout.Elements.Add(label);
  171. var variable = new DigitalFormVariable();
  172. variable.Form.CopyFrom(question.Form);
  173. variable.Sequence = sequence++;
  174. DFLayoutFieldProperties properties;
  175. Type fieldType;
  176. var code = GenerateCode(question.Code.NotWhiteSpaceOr(question.Answer.ToString()));
  177. var parameters = question.ParseParameters();
  178. switch (question.Answer)
  179. {
  180. case QAAnswer.Choice:
  181. {
  182. // ColourExpression
  183. var buttons = parameters["Options"].Split(',').ToArray(x => x.Trim());
  184. var colors = parameters["Colors"].Split(',').ToArray(x => x.Trim());
  185. var defValue = parameters["Default"].Trim();
  186. fieldType = typeof(DFLayoutOptionField);
  187. var optionProperties = new DFLayoutOptionFieldProperties();
  188. properties = optionProperties;
  189. optionProperties.Default = defValue;
  190. optionProperties.OptionType = DFLayoutOptionType.Buttons;
  191. optionProperties.Options = DFLayoutOptionFieldProperties.WriteOptions(buttons);
  192. var colourExpression = "null";
  193. foreach(var (option, colour) in buttons.Zip(colors))
  194. {
  195. colourExpression = $"If([{code}] == {QuoteString(option)}, {QuoteString(colour)}, {colourExpression})";
  196. }
  197. optionProperties.ColourExpression = colourExpression;
  198. nButtons = Math.Max(nButtons, buttons.Length);
  199. }
  200. break;
  201. case QAAnswer.Number:
  202. {
  203. var defValue = parameters["Default"];
  204. fieldType = typeof(DFLayoutDoubleField);
  205. var doubleProperties = new DFLayoutDoubleFieldProperties();
  206. properties = doubleProperties;
  207. doubleProperties.Default = double.TryParse(defValue, out var d) ? d : default;
  208. }
  209. break;
  210. case QAAnswer.Text:
  211. {
  212. var defValue = parameters["Default"];
  213. fieldType = typeof(DFLayoutStringField);
  214. var stringProperties = new DFLayoutStringFieldProperties();
  215. properties = stringProperties;
  216. stringProperties.Default = defValue;
  217. }
  218. break;
  219. case QAAnswer.Combo:
  220. {
  221. var buttons = parameters["Options"].Split(',');
  222. var defValue = parameters["Default"];
  223. fieldType = typeof(DFLayoutOptionField);
  224. var optionProperties = new DFLayoutOptionFieldProperties();
  225. properties = optionProperties;
  226. optionProperties.Default = defValue;
  227. optionProperties.OptionType = DFLayoutOptionType.Combo;
  228. optionProperties.Options = DFLayoutOptionFieldProperties.WriteOptions(buttons);
  229. }
  230. break;
  231. default:
  232. throw new Exception("Impossible");
  233. }
  234. properties.Code = code;
  235. properties.Description = question.Description.NotWhiteSpaceOr(question.Question);
  236. properties.Required = parameters.GetValueOrDefault("Default").IsNullOrWhiteSpace();
  237. variable.SaveProperties(fieldType, properties);
  238. mappings.Add(question.ID, variable.Code);
  239. codes.Add(variable.Code);
  240. var field = (Activator.CreateInstance(variable.FieldType()) as DFLayoutField)!;
  241. field.Name = variable.Code;
  242. field.Row = row;
  243. field.Column = 3;
  244. layout.Elements.Add(field);
  245. variables.Add(variable);
  246. ++i;
  247. }
  248. question.Converted = true;
  249. }
  250. layout.ColumnWidths.Add(Math.Max(150, nButtons * 80).ToString());
  251. var dfLayout = new DigitalFormLayout();
  252. dfLayout.Form.ID = item.FormID;
  253. dfLayout.Layout = layout.SaveLayout();
  254. dfLayout.Description = "Generated from QA form";
  255. dfLayout.Type = DFLayoutType.Desktop;
  256. dfLayout.Active = true;
  257. layouts.Add(dfLayout);
  258. }
  259. provider.Save(variables);
  260. provider.Save(layouts);
  261. provider.Save(qaQuestions.SelectMany(x => x.Questions));
  262. FormUpdater.UpdateAllForms(
  263. (form, variables) => false,
  264. (formType, instance, form, variables) =>
  265. {
  266. if (!variableMappings.TryGetValue(form.ID, out var mappings)) return false;
  267. var values = DigitalForm.DeserializeFormSaveData(instance) ?? new();
  268. var items = values.ToLoadStorage().Items().ToArray();
  269. foreach(var (key, value) in items)
  270. {
  271. if (!Guid.TryParse(key, out var id)) continue;
  272. if (!mappings.TryGetValue(id, out var code)) continue;
  273. values.AddValue(code, value?.ToString()?.Trim());
  274. }
  275. DigitalForm.SerializeFormData(instance, values);
  276. return true;
  277. },
  278. filter: Filter<DigitalForm>.Where(x => x.ID).InList(qaQuestions.ToArray(x => x.FormID)));
  279. }
  280. public override bool Update()
  281. {
  282. var provider = DbFactory.NewProvider(Logger.Main);
  283. UpdateTimeSheets(provider);
  284. ConvertLink<Assignment, JobLink>(provider, x => x.JobLink, x => x.Job);
  285. ConvertLink<EquipmentAssignment, JobLink>(provider, x => x.JobLink, x => x.Job);
  286. ConvertLink<Kanban, JobLink>(provider, x => x.JobLink, x => x.Job);
  287. ConvertLink<Requisition, JobLink>(provider, x => x.JobLink, x => x.Job);
  288. ConvertLink<RequisitionItem, JobLink>(provider, x => x.JobLink, x => x.Job);
  289. ConvertLink<Setout, JobLink>(provider, x => x.JobLink, x => x.Job);
  290. ConvertLink<JobBillOfMaterialsActivity, JobLink>(provider, x => x.JobLink, x => x.Job);
  291. ConvertLink<ManufacturingSection, QAFormLink, DigitalFormLink>(provider, x => x.QAForm, x => x.DigitalForm);
  292. ConvertLink<ManufacturingTemplateStage, QAFormLink, DigitalFormLink>(provider, x => x.QAForm, x => x.DigitalForm);
  293. ConvertQAQuestions(provider);
  294. return true;
  295. }
  296. }