JobBillOfMaterialsGrid.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using Comal.Classes;
  9. using Comal.Classes.SecurityDescriptors;
  10. using InABox.Clients;
  11. using InABox.Configuration;
  12. using InABox.Core;
  13. using InABox.DynamicGrid;
  14. using InABox.Integration.Logikal;
  15. using InABox.Integration.V6;
  16. using InABox.Wpf;
  17. using InABox.WPF;
  18. using PRSDesktop.Integrations.Common;
  19. using PRSDesktop.Integrations.Logikal;
  20. using PRSDesktop.Integrations.V6;
  21. namespace PRSDesktop
  22. {
  23. internal class JobBillOfMaterialsGrid : DynamicDataGrid<JobBillOfMaterials>, IMasterDetailControl<Job,JobBillOfMaterials>
  24. {
  25. private readonly Button _approve;
  26. public Job? Master { get; set; }
  27. public Filter<JobBillOfMaterials> MasterDetailFilter => (Master?.ID ?? Guid.Empty) != Guid.Empty
  28. ? new Filter<JobBillOfMaterials>(x => x.Job.ID).IsEqualTo(Master.ID)
  29. : new Filter<JobBillOfMaterials>().None();
  30. public JobBillOfMaterialsGrid()
  31. {
  32. HiddenColumns.Add(x => x.Approved);
  33. HiddenColumns.Add(x => x.Job.ID);
  34. if (Security.IsAllowed<CanApproveBillsOfMaterials>())
  35. _approve = AddButton("Approve", null, ApproveClick);
  36. }
  37. protected override void DoReconfigure(DynamicGridOptions options)
  38. {
  39. base.DoReconfigure(options);
  40. options.AddRows = true;
  41. options.EditRows = true;
  42. options.DeleteRows = true;
  43. options.FilterRows = true;
  44. options.HideDatabaseFilters = true;
  45. options.SelectColumns = true;
  46. options.RecordCount = true;
  47. options.ReorderRows = false;
  48. }
  49. private bool ApproveClick(Button button, CoreRow[] rows)
  50. {
  51. if (rows == null || !rows.Any())
  52. {
  53. MessageBox.Show("Please select a row first!");
  54. return false;
  55. }
  56. var bom = rows[0].ToObject<JobBillOfMaterials>();
  57. bom.Approved = bom.Approved.IsEmpty() ? DateTime.Now : DateTime.MinValue;
  58. UpdateRow<DateTime?>(rows[0], "Approved", bom.Approved.IsEmpty() ? null : bom.Approved, true);
  59. new Client<JobBillOfMaterials>().Save(bom, bom.Approved.IsEmpty() ? "Cleared Approval" : "Marked as Approved");
  60. UpdateButton(_approve, null,
  61. _approve.IsEnabled && !bom.Approved.IsEmpty() ? "Unapprove" : "Approve");
  62. return false;
  63. }
  64. protected override void SelectItems(CoreRow[] rows)
  65. {
  66. base.SelectItems(rows);
  67. if (rows?.Length == 1)
  68. {
  69. _approve.Visibility = Visibility.Visible;
  70. UpdateButton(_approve, null,
  71. _approve.IsEnabled && !rows[0].Get<JobBillOfMaterials, DateTime>(c => c.Approved).IsEmpty() ? "Unapprove" : "Approve");
  72. }
  73. else
  74. _approve.Visibility = Visibility.Collapsed;
  75. }
  76. protected override void Reload(
  77. Filters<JobBillOfMaterials> criteria, Columns<JobBillOfMaterials> columns, ref SortOrder<JobBillOfMaterials>? sort,
  78. CancellationToken token, Action<CoreTable?, Exception?> action)
  79. {
  80. criteria.Add(MasterDetailFilter);
  81. base.Reload(criteria, columns, ref sort, token, action);
  82. }
  83. protected override bool CanCreateItems()
  84. {
  85. return base.CanCreateItems() && ((Master?.ID ?? Guid.Empty) != Guid.Empty);
  86. }
  87. public override JobBillOfMaterials CreateItem()
  88. {
  89. var result = base.CreateItem();
  90. result.Job.ID = Master?.ID ?? Guid.Empty;
  91. result.Job.Synchronise(Master ?? new Job());
  92. return result;
  93. }
  94. private LogikalSettings? _logikalSettings;
  95. private V6Settings? _v6Settings;
  96. protected override void DoAdd(bool openEditorOnDirectEdit = false)
  97. {
  98. ContextMenu? menu = null;
  99. _v6Settings ??= new GlobalConfiguration<V6Settings>().Load();
  100. if (_v6Settings.CanImport<ImportV6BillsOfMaterials>(_v6Settings.ImportBoms, Master))
  101. {
  102. menu ??= new ContextMenu();
  103. var profiles = new MenuItem()
  104. {
  105. Header = "Import from V6",
  106. Icon = new Image() { Source = PRSDesktop.Resources.v6.AsBitmapImage() }
  107. };
  108. profiles.Click += ImportFromV6;
  109. menu.Items.Add(profiles);
  110. }
  111. _logikalSettings ??= new GlobalConfiguration<LogikalSettings>().Load();
  112. if (_logikalSettings.CanImport<ImportLogikalBillsOfMaterials>(_logikalSettings.ImportBoms, Master))
  113. {
  114. menu ??= new ContextMenu();
  115. var item = new MenuItem()
  116. {
  117. Header = "Import from Logikal",
  118. Icon = new Image() { Source = PRSDesktop.Resources.logikal.AsBitmapImage() }
  119. };
  120. item.Click += ImportFromLogikal;
  121. menu.Items.Add(item);
  122. }
  123. if (menu != null)
  124. {
  125. menu.Items.Insert(0,new Separator());
  126. var item = new MenuItem()
  127. {
  128. Header = "New Bill Of Materials"
  129. };
  130. item.Click += (o, e) => base.DoAdd(openEditorOnDirectEdit);
  131. menu.Items.Insert(0,item);
  132. menu.IsOpen = true;
  133. }
  134. else
  135. base.DoAdd(openEditorOnDirectEdit);
  136. }
  137. private List<JobBillOfMaterialsItem> materials = new();
  138. private List<JobBillOfMaterialsActivity> activities = new();
  139. private void ImportFromLogikal(object sender, RoutedEventArgs e)
  140. {
  141. if (Master == null)
  142. return;
  143. LogikalClient.Instance.Initialize()
  144. .Error(error => MessageWindow.ShowMessage($"Unable to locate Logikal App!\n\n{error.Message}", "Error"))
  145. .Success<LogikalInitializeResponse>(initialize =>
  146. {
  147. LogikalClient.Instance.Connect(initialize.Path)
  148. .Error(error => MessageWindow.ShowMessage($"Unable to connect to Logikal!\n\n{initialize.Path}\n\n{error.Message}", "Error"))
  149. .Success<LogikalConnectResponse>(connect =>
  150. {
  151. LogikalClient.Instance.Login()
  152. .Error(error =>
  153. MessageWindow.ShowMessage($"Unable to login to Logikal!\n\n{error.Message}",
  154. "Error"))
  155. .Success<LogikalLoginResponse>(login =>
  156. {
  157. ImportBOMFromLogikal();
  158. });
  159. });
  160. });
  161. }
  162. private void ImportBOMFromLogikal()
  163. {
  164. materials.Clear();
  165. activities.Clear();
  166. var import = new LogikalElevationSelection(Master, LogikalElevationSelectionType.BOM, (project, boms) =>
  167. {
  168. var bom = boms.FirstOrDefault();
  169. if (bom != null)
  170. {
  171. if (_logikalSettings.SaveFiles)
  172. {
  173. File.WriteAllBytes(
  174. Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
  175. "BillOfMaterials.xlsx"), bom.ExcelData);
  176. File.WriteAllBytes(
  177. Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
  178. "BillOfMaterials.sqlite"), bom.SQLiteData);
  179. }
  180. AWGMappingWindow window = new AWGMappingWindow(
  181. IntegrationSourceType.Logikal,
  182. bom.Finishes,
  183. bom.Profiles,
  184. bom.Gaskets,
  185. bom.Components,
  186. bom.Glass,
  187. bom.Labour,
  188. CreateBOMPart,
  189. CreateBOMLabour);
  190. var result = window.ShowDialog();
  191. return result == true;
  192. }
  193. return false;
  194. });
  195. if (import.ShowDialog() == true && (materials.Any() || activities.Any()))
  196. CreateBOM();
  197. }
  198. private void CreateBOM()
  199. {
  200. Progress.ShowModal("Creating Bill of Materials", progress =>
  201. {
  202. var bom = new JobBillOfMaterials();
  203. bom.Job.CopyFrom(Master);
  204. bom.Description = $"BOM Imported {DateTime.Now}";
  205. Client.Save(bom, "Imported From Logikal");
  206. progress.Report($"Saving Materials ({materials.Count})");
  207. materials.SortBy(x=>x.Product.Code);
  208. foreach (var material in materials)
  209. {
  210. material.BillOfMaterials.CopyFrom(bom);
  211. material.Job.CopyFrom(Master);
  212. }
  213. if (materials.Any())
  214. Client.Save(materials,"Imported From Logikal");
  215. progress.Report($"Saving Labour ({activities.Count})");
  216. activities.SortBy(x=>x.ActivityLink.Code);
  217. foreach (var activity in activities)
  218. {
  219. activity.BillOfMaterials.CopyFrom(bom);
  220. activity.JobLink.CopyFrom(Master);
  221. }
  222. if (activities.Any())
  223. Client.Save(activities,"Imported From Logikal");
  224. });
  225. Refresh(false,true);
  226. }
  227. private void CreateBOMPart(ProductLink product, ProductStyleLink? style, IBaseDimensions dimensions, double quantity, double cost)
  228. {
  229. var item = new JobBillOfMaterialsItem();
  230. item.Product.CopyFrom(product);
  231. item.Dimensions.Unit.CopyFrom(product.UnitOfMeasure);
  232. item.Dimensions.CopyFrom(dimensions);
  233. item.Dimensions.CalculateValueAndUnitSize();
  234. item.Style.CopyFrom(style ?? new ProductStyleLink());
  235. item.Quantity = quantity;
  236. item.UnitCost = cost;
  237. materials.Add(item);
  238. }
  239. private void CreateBOMLabour(ActivityLink activity, TimeSpan duration, double cost)
  240. {
  241. var item = activities.FirstOrDefault(x => x.ActivityLink.ID == activity.ID);
  242. if (item == null)
  243. {
  244. item = new JobBillOfMaterialsActivity();
  245. item.ActivityLink.CopyFrom(activity);
  246. activities.Add(item);
  247. }
  248. var total = item.TotalCost;
  249. item.Duration += duration;
  250. item.TotalCost = total + (duration.TotalHours * cost);
  251. }
  252. private void ImportFromV6(object sender, RoutedEventArgs e)
  253. {
  254. _v6Settings ??= new GlobalConfiguration<V6Settings>().Load();
  255. var _client = new V6Client();
  256. if (!_client.Connect())
  257. {
  258. MessageBox.Show("Unable to connect to V6");
  259. return;
  260. }
  261. var _project = _client.GetProject(Master?.JobNumber, Master?.SourceRef);
  262. if (_project == null)
  263. {
  264. MessageBox.Show("This is not a V6 project!");
  265. return;
  266. }
  267. materials.Clear();
  268. activities.Clear();
  269. var import = new V6VariationSelection(_client, _project, _ => true, (variation) =>
  270. {
  271. if (Master == null)
  272. return false;
  273. var scopes = Client.Query(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(Master.ID), Columns.Required<JobScope>())
  274. .ToArray<JobScope>();
  275. var scope = scopes.FirstOrDefault(x => string.Equals(x.SourceRef ?? "", string.IsNullOrWhiteSpace(variation.ID) ? "" : variation.GetReference()));
  276. if (scope == null && MessageWindow.ShowOKCancel("Create Variation?", "Confirm"))
  277. {
  278. scope = new JobScope();
  279. scope.Job.ID = Master.ID;
  280. scope.Description = variation.Description;
  281. _v6Settings ??= new GlobalConfiguration<V6Settings>().Load();
  282. if (_v6Settings.UseV6QuoteNumber)
  283. scope.Number = variation.GetReference();
  284. scope.ExTax = variation.SellPrice;
  285. Client.Save(scope,"Created from Bill of Materials Import");
  286. }
  287. if (scope == null)
  288. return false;
  289. var bom = _client.GetBOM(_project,variation.ID);
  290. AWGMappingWindow window = new AWGMappingWindow(
  291. IntegrationSourceType.V6,
  292. bom.Finishes,
  293. bom.Profiles,
  294. bom.Gaskets,
  295. bom.Components,
  296. bom.Glass,
  297. bom.Labour,
  298. CreateBOMPart,
  299. CreateBOMLabour);
  300. var result = window.ShowDialog();
  301. return result == true;
  302. });
  303. if (import.ShowDialog() == true)
  304. CreateBOM();
  305. }
  306. }
  307. }