using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Windows; using System.Windows.Controls; using Comal.Classes; using Comal.Classes.SecurityDescriptors; using InABox.Clients; using InABox.Configuration; using InABox.Core; using InABox.DynamicGrid; using InABox.Integration.Logikal; using InABox.Integration.V6; using InABox.Wpf; using InABox.WPF; using NPOI.Util; using PRSDesktop.Integrations.Common; using PRSDesktop.Integrations.Logikal; using PRSDesktop.Integrations.V6; namespace PRSDesktop { internal class JobBillOfMaterialsGrid : DynamicDataGrid, IMasterDetailControl { private readonly Button _approve; public Job? Master { get; set; } public Filter MasterDetailFilter => (Master?.ID ?? Guid.Empty) != Guid.Empty ? new Filter(x => x.Job.ID).IsEqualTo(Master.ID) : new Filter().None(); public JobBillOfMaterialsGrid() { HiddenColumns.Add(x => x.Approved); HiddenColumns.Add(x => x.Job.ID); if (Security.IsAllowed()) _approve = AddButton("Approve", null, ApproveClick); } protected override void DoReconfigure(DynamicGridOptions options) { base.DoReconfigure(options); options.AddRows = true; options.EditRows = true; options.DeleteRows = true; options.FilterRows = true; options.HideDatabaseFilters = true; options.SelectColumns = true; options.RecordCount = true; options.ReorderRows = false; } private bool ApproveClick(Button button, CoreRow[] rows) { if (rows == null || !rows.Any()) { MessageBox.Show("Please select a row first!"); return false; } var bom = rows[0].ToObject(); bom.Approved = bom.Approved.IsEmpty() ? DateTime.Now : DateTime.MinValue; UpdateRow(rows[0], "Approved", bom.Approved.IsEmpty() ? null : bom.Approved, true); new Client().Save(bom, bom.Approved.IsEmpty() ? "Cleared Approval" : "Marked as Approved"); UpdateButton(_approve, null, _approve.IsEnabled && !bom.Approved.IsEmpty() ? "Unapprove" : "Approve"); return false; } protected override void SelectItems(CoreRow[] rows) { base.SelectItems(rows); if (rows?.Length == 1) { _approve.Visibility = Visibility.Visible; UpdateButton(_approve, null, _approve.IsEnabled && !rows[0].Get(c => c.Approved).IsEmpty() ? "Unapprove" : "Approve"); } else _approve.Visibility = Visibility.Collapsed; } protected override void Reload( Filters criteria, Columns columns, ref SortOrder? sort, CancellationToken token, Action action) { criteria.Add(MasterDetailFilter); base.Reload(criteria, columns, ref sort, token, action); } protected override bool CanCreateItems() { return base.CanCreateItems() && ((Master?.ID ?? Guid.Empty) != Guid.Empty); } public override JobBillOfMaterials CreateItem() { var result = base.CreateItem(); result.Job.ID = Master?.ID ?? Guid.Empty; result.Job.Synchronise(Master ?? new Job()); return result; } private LogikalSettings? _logikalSettings; private V6Settings? _v6Settings; protected override void DoAdd(bool openEditorOnDirectEdit = false) { ContextMenu? menu = null; _v6Settings ??= new GlobalConfiguration().Load(); if (_v6Settings.CanImport(_v6Settings.ImportBoms, Master)) { menu ??= new ContextMenu(); var profiles = new MenuItem() { Header = "Import from V6", Icon = new Image() { Source = PRSDesktop.Resources.v6.AsBitmapImage() } }; profiles.Click += ImportFromV6; menu.Items.Add(profiles); } _logikalSettings ??= new GlobalConfiguration().Load(); if (_logikalSettings.CanImport(_logikalSettings.ImportBoms, Master)) { menu ??= new ContextMenu(); var item = new MenuItem() { Header = "Import from Logikal", Icon = new Image() { Source = PRSDesktop.Resources.logikal.AsBitmapImage() } }; item.Click += ImportFromLogikal; menu.Items.Add(item); } if (menu != null) { menu.Items.Insert(0,new Separator()); var item = new MenuItem() { Header = "New Bill Of Materials" }; item.Click += (o, e) => base.DoAdd(openEditorOnDirectEdit); menu.Items.Insert(0,item); menu.IsOpen = true; } else base.DoAdd(openEditorOnDirectEdit); } private List materials = new(); private List activities = new(); private void ImportFromLogikal(object sender, RoutedEventArgs e) { if (Master == null) return; LogikalClient.Instance.Initialize() .Error(error => MessageWindow.ShowMessage($"Unable to locate Logikal App!\n\n{error.Message}", "Error")) .Success(initialize => { LogikalClient.Instance.Connect(initialize.Path) .Error(error => MessageWindow.ShowMessage($"Unable to connect to Logikal!\n\n{initialize.Path}\n\n{error.Message}", "Error")) .Success(connect => { LogikalClient.Instance.Login() .Error(error => MessageWindow.ShowMessage($"Unable to login to Logikal!\n\n{error.Message}", "Error")) .Success(login => { ImportBOMFromLogikal(); }); }); }); } private void ImportBOMFromLogikal() { materials.Clear(); activities.Clear(); byte[] exceldata = null; var import = new LogikalElevationSelection(Master, LogikalElevationSelectionType.BOM, (project, boms) => { var bom = boms.FirstOrDefault(); if (bom != null) { exceldata = bom.ExcelData; if (_logikalSettings.SaveFiles) { File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "BillOfMaterials.xlsx"), bom.ExcelData); File.WriteAllBytes( Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "BillOfMaterials.sqlite"), bom.SQLiteData); } AWGMappingWindow window = new AWGMappingWindow( IntegrationSourceType.Logikal, bom.Styles, bom.Groups, bom.Suppliers, bom.Profiles, bom.Gaskets, bom.Components, bom.Glass, bom.Labour, CreateBOMPart, CreateBOMLabour); var result = window.ShowDialog(); return result == true; } return false; }); if (import.ShowDialog() == true && (materials.Any() || activities.Any())) { var bom = CreateBOM(null); if (exceldata != null) { var ss = new JobSpreadsheet(); ss.Code = $"BOM {bom.Number}"; ss.Description = $"Logikal BOM {bom.Number} Imported {DateTime.Now:f}"; ss.Data = exceldata; ss.Parent.ID = Master.ID; Client.Save(ss, "Imported From Logikal"); } } } private JobBillOfMaterials CreateBOM(JobScope? scope) { JobBillOfMaterials bom = new(); Progress.ShowModal("Creating Bill of Materials", progress => { bom.Job.CopyFrom(Master); bom.Description = $"BOM Imported {DateTime.Now}"; Client.Save(bom, "Imported From Logikal"); progress.Report($"Saving Materials ({materials.Count})"); materials.SortBy(x=>x.Product.Code); foreach (var material in materials) { material.BillOfMaterials.CopyFrom(bom); material.Job.CopyFrom(Master); material.Scope.CopyFrom(scope ?? new JobScope()); } if (materials.Any()) Client.Save(materials,"Imported From Logikal"); progress.Report($"Saving Labour ({activities.Count})"); activities.SortBy(x=>x.ActivityLink.Code); foreach (var activity in activities) { activity.BillOfMaterials.CopyFrom(bom); activity.JobLink.CopyFrom(Master); activity.Scope.CopyFrom(scope ?? new JobScope()); } if (activities.Any()) Client.Save(activities,"Imported From Logikal"); }); Refresh(false,true); return bom; } private void CreateBOMPart(ProductLink product, ProductStyleLink? style, IBaseDimensions dimensions, double quantity, double cost) { var item = new JobBillOfMaterialsItem(); item.Product.CopyFrom(product); item.Dimensions.Unit.CopyFrom(product.UnitOfMeasure); item.Dimensions.CopyFrom(dimensions); item.Dimensions.CalculateValueAndUnitSize(); item.Style.CopyFrom(style ?? new ProductStyleLink()); item.Quantity = quantity; item.TotalCost = cost; materials.Add(item); } private void CreateBOMLabour(ActivityLink activity, TimeSpan duration, double cost) { var item = activities.FirstOrDefault(x => x.ActivityLink.ID == activity.ID); if (item == null) { item = new JobBillOfMaterialsActivity(); item.ActivityLink.CopyFrom(activity); activities.Add(item); } var total = item.TotalCost; item.Duration += duration; item.TotalCost = total + (duration.TotalHours * cost); } private void ImportFromV6(object sender, RoutedEventArgs e) { _v6Settings ??= new GlobalConfiguration().Load(); var _client = new V6Client(); if (!_client.Connect()) { MessageBox.Show("Unable to connect to V6"); return; } var _project = _client.GetProject(Master?.JobNumber, Master?.SourceRef); if (_project == null) { MessageBox.Show("This is not a V6 project!"); return; } materials.Clear(); activities.Clear(); JobScope scope = new JobScope(); var import = new V6VariationSelection(_client, _project, _ => true, (variation) => { if (Master == null) return false; var scopes = Client.Query(new Filter(x => x.Job.ID).IsEqualTo(Master.ID), Columns.Required().Add(x=>x.SourceRef)) .ToArray(); scope = scopes.FirstOrDefault(x => string.Equals(x.SourceRef ?? "", string.IsNullOrWhiteSpace(variation.ID) ? "" : variation.GetReference())); if (scope == null && MessageWindow.ShowOKCancel("Create Variation?", "Confirm")) { scope = new JobScope(); scope.Job.ID = Master.ID; scope.Description = variation.Description; _v6Settings ??= new GlobalConfiguration().Load(); if (_v6Settings.UseV6QuoteNumber) scope.Number = variation.GetReference(); scope.ExTax = variation.SellPrice; Client.Save(scope,"Created from Bill of Materials Import"); } if (scope == null) return false; var bom = _client.GetBOM(_project,variation.ID); AWGMappingWindow window = new AWGMappingWindow( IntegrationSourceType.V6, bom.Styles, bom.Groups, bom.Suppliers, bom.Profiles, bom.Gaskets, bom.Components, bom.Glass, bom.Labour, CreateBOMPart, CreateBOMLabour); var result = window.ShowDialog(); return result == true; }); if (import.ShowDialog() == true) CreateBOM(scope); } } }