JobBillOfMaterialsGrid.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  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/ReynaPro",
  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. var _ssFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
  177. "BillOfMaterials.xlsx");
  178. try
  179. {
  180. File.WriteAllBytes(_ssFile, bom.ExcelData);
  181. }
  182. catch (Exception e)
  183. {
  184. MessageWindow.ShowError($"Unable to Save File: {_ssFile}",e);
  185. }
  186. var _sqlFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
  187. "BillOfMaterials.sqlite");
  188. try
  189. {
  190. File.WriteAllBytes(_sqlFile, bom.SQLiteData);
  191. }
  192. catch (Exception e)
  193. {
  194. MessageWindow.ShowError($"Unable to Save File: {_sqlFile}",e);
  195. }
  196. }
  197. AWGMappingWindow window = new AWGMappingWindow(
  198. IntegrationSourceType.Logikal,
  199. bom.Styles,
  200. bom.Groups,
  201. bom.Suppliers,
  202. bom.Discounts,
  203. bom.Profiles,
  204. bom.Gaskets,
  205. bom.Components,
  206. bom.Glass,
  207. bom.Labour,
  208. CreateDiscount,
  209. CreateBOMPart,
  210. CreateBOMLabour);
  211. var result = window.ShowDialog();
  212. return result == true;
  213. }
  214. return false;
  215. });
  216. if (import.ShowDialog() == true && (materials.Any() || activities.Any()))
  217. {
  218. var bom = CreateBOM(null);
  219. if (exceldata != null)
  220. {
  221. var ss = new JobSpreadsheet();
  222. ss.Code = $"BOM {bom.Number}";
  223. ss.Description = $"Logikal BOM {bom.Number} Imported {DateTime.Now:f}";
  224. ss.Data = exceldata;
  225. ss.Parent.ID = Master.ID;
  226. Client.Save(ss, "Imported From Logikal");
  227. }
  228. }
  229. }
  230. private void CreateDiscount(SupplierDiscountGroupLink group, double value)
  231. {
  232. var discounts = Client.Query(
  233. new Filter<SupplierDiscount>(x => x.Group.ID).IsEqualTo(group.ID)
  234. .And(
  235. new Filter<SupplierDiscount>(x => x.Job.ID).IsEqualTo(Master.ID)
  236. .Or(x=>x.Job.ID).IsEqualTo(Guid.Empty)
  237. ),
  238. Columns.Local<SupplierDiscount>()
  239. ).Rows.ToArray<SupplierDiscount>();
  240. var general = discounts.FirstOrDefault(x => x.Job.ID == Guid.Empty);
  241. if (general == null)
  242. {
  243. general = new SupplierDiscount();
  244. general.Group.ID = group.ID;
  245. Client.Save(general,"Created by BOM Import");
  246. }
  247. var supplierdiscount = discounts.FirstOrDefault(x=>x.Job.ID == Master.ID);
  248. if (value.IsEffectivelyEqual(general.Value))
  249. {
  250. if (supplierdiscount != null)
  251. Client.Delete(supplierdiscount,"Merged with General Discount");
  252. }
  253. else
  254. {
  255. if (supplierdiscount == null)
  256. {
  257. supplierdiscount = new SupplierDiscount();
  258. supplierdiscount.Group.ID = group.ID;
  259. supplierdiscount.Job.ID = Master.ID;
  260. }
  261. supplierdiscount.Value = value;
  262. Client.Save(supplierdiscount,"Created by BOM Import");
  263. }
  264. }
  265. private JobBillOfMaterials CreateBOM(JobScope? scope)
  266. {
  267. JobBillOfMaterials bom = new();
  268. Progress.ShowModal("Creating Bill of Materials", progress =>
  269. {
  270. bom.Job.CopyFrom(Master);
  271. bom.Description = $"BOM Imported {DateTime.Now}";
  272. Client.Save(bom, "Imported From Logikal");
  273. progress.Report($"Saving Materials ({materials.Count})");
  274. materials.SortBy(x=>x.Product.Code);
  275. foreach (var material in materials)
  276. {
  277. material.BillOfMaterials.CopyFrom(bom);
  278. material.Job.CopyFrom(Master);
  279. material.Scope.CopyFrom(scope ?? new JobScope());
  280. }
  281. if (materials.Any())
  282. Client.Save(materials,"Imported From Logikal");
  283. progress.Report($"Saving Labour ({activities.Count})");
  284. activities.SortBy(x=>x.ActivityLink.Code);
  285. foreach (var activity in activities)
  286. {
  287. activity.BillOfMaterials.CopyFrom(bom);
  288. activity.JobLink.CopyFrom(Master);
  289. activity.Scope.CopyFrom(scope ?? new JobScope());
  290. }
  291. if (activities.Any())
  292. Client.Save(activities,"Imported From Logikal");
  293. });
  294. Refresh(false,true);
  295. return bom;
  296. }
  297. private void CreateBOMPart(ProductLink product, ProductStyleLink? style, IBaseDimensions dimensions, double quantity, double cost)
  298. {
  299. var item = new JobBillOfMaterialsItem();
  300. item.Product.CopyFrom(product);
  301. item.Dimensions.Unit.CopyFrom(product.UnitOfMeasure);
  302. item.Dimensions.CopyFrom(dimensions);
  303. item.Dimensions.CalculateValueAndUnitSize();
  304. item.Style.CopyFrom(style ?? new ProductStyleLink());
  305. item.Quantity = quantity;
  306. item.TotalCost = cost;
  307. materials.Add(item);
  308. }
  309. private void CreateBOMLabour(ActivityLink activity, TimeSpan duration, double cost)
  310. {
  311. var item = activities.FirstOrDefault(x => x.ActivityLink.ID == activity.ID);
  312. if (item == null)
  313. {
  314. item = new JobBillOfMaterialsActivity();
  315. item.ActivityLink.CopyFrom(activity);
  316. activities.Add(item);
  317. }
  318. var total = item.TotalCost;
  319. item.Duration += duration;
  320. item.TotalCost = total + (duration.TotalHours * cost);
  321. }
  322. private void ImportFromV6(object sender, RoutedEventArgs e)
  323. {
  324. _v6Settings ??= new GlobalConfiguration<V6Settings>().Load();
  325. var _client = new V6Client();
  326. if (!_client.Connect())
  327. {
  328. MessageBox.Show("Unable to connect to V6");
  329. return;
  330. }
  331. var _project = _client.GetProject(Master?.JobNumber, Master?.SourceRef);
  332. if (_project == null)
  333. {
  334. MessageBox.Show("This is not a V6 project!");
  335. return;
  336. }
  337. materials.Clear();
  338. activities.Clear();
  339. JobScope scope = new JobScope();
  340. var import = new V6VariationSelection(_client, _project, _ => true, (variation) =>
  341. {
  342. if (Master == null)
  343. return false;
  344. var scopes = Client.Query(new Filter<JobScope>(x => x.Job.ID).IsEqualTo(Master.ID), Columns.Required<JobScope>().Add(x=>x.SourceRef))
  345. .ToArray<JobScope>();
  346. scope = scopes.FirstOrDefault(x => string.Equals(x.SourceRef ?? "", string.IsNullOrWhiteSpace(variation.ID) ? "" : variation.GetReference()));
  347. if (scope == null && MessageWindow.ShowOKCancel("Create Variation?", "Confirm"))
  348. {
  349. scope = new JobScope();
  350. scope.Job.ID = Master.ID;
  351. scope.Description = variation.Description;
  352. _v6Settings ??= new GlobalConfiguration<V6Settings>().Load();
  353. if (_v6Settings.UseV6QuoteNumber)
  354. scope.Number = variation.GetReference();
  355. scope.ExTax = variation.SellPrice;
  356. Client.Save(scope,"Created from Bill of Materials Import");
  357. }
  358. if (scope == null)
  359. return false;
  360. var bom = _client.GetBOM(_project,variation.ID);
  361. AWGMappingWindow window = new AWGMappingWindow(
  362. IntegrationSourceType.V6,
  363. bom.Styles,
  364. bom.Groups,
  365. bom.Suppliers,
  366. bom.Discounts,
  367. bom.Profiles,
  368. bom.Gaskets,
  369. bom.Components,
  370. bom.Glass,
  371. bom.Labour,
  372. CreateDiscount,
  373. CreateBOMPart,
  374. CreateBOMLabour);
  375. var result = window.ShowDialog();
  376. return result == true;
  377. });
  378. if (import.ShowDialog() == true)
  379. CreateBOM(scope);
  380. }
  381. }
  382. }