JobBillOfMaterialsGrid.cs 14 KB

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