using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using InABox.Core; using PRSClasses; namespace Comal.Classes { [Caption("Materials")] public class JobBillOfMaterialsItem : StockEntity, IRemotable, IPersistent, IOneToMany, IOneToMany, ISequenceable, ILicense, IJobMaterial, IIssues, IProblems { [EntityRelationship(DeleteAction.Cascade)] [Editable(Editable.Hidden)] public JobLink Job { get; set; } [EntityRelationship(DeleteAction.Cascade)] [Editable(Editable.Hidden)] public JobBillOfMaterialsLink BillOfMaterials { get; set; } [EditorSequence(1)] [EntityRelationship(DeleteAction.SetNull)] [RequiredColumn] public override ProductLink Product { get; set; } [EditorSequence(2)] [EntityRelationship(DeleteAction.SetNull)] public ProductStyleLink Style { get; set; } [NullEditor] [Obsolete("Replaced by Dimensions",true)] public double UnitSize { get; set; } [EditorSequence(3)] [RequiredColumn] [DimensionsEditor(typeof(StockDimensions))] public override StockDimensions Dimensions { get; set; } [EditorSequence(4)] [RequiredColumn] public JobScopeLink Scope { get; set; } [EditorSequence(5)] [DoubleEditor(Summary = Summary.Sum)] public double Quantity { get; set; } [EditorSequence(6)] public double UnitCost { get; set; } [EditorSequence(7)] [CurrencyEditor(Summary = Summary.Sum)] public double TotalCost { get; set; } [EditorSequence(8)] [TextBoxEditor] public string Section { get; set; } [EditorSequence(9)] public SupplierLink Supplier { get; set; } private class JobITPLookup : LookupDefinitionGenerator { public override Filter DefineFilter(JobBillOfMaterialsItem[] items) { if (items.Length == 1) return new Filter(x => x.Job.ID).IsEqualTo(items.First().Job.ID); return LookupFactory.DefineFilter(); } public override Columns DefineFilterColumns() => Columns.None().Add(x => x.Job.ID); } [EditorSequence(10)] [EntityRelationship(DeleteAction.SetNull)] [LookupDefinition(typeof(JobITPLookup))] public JobITPLink ITP { get; set; } [NullEditor] public long Sequence { get; set; } public PurchaseOrderItemLink PurchaseOrderItem { get; set; } public ManufacturingPacketLink Packet { get; set; } [NullEditor] [Obsolete("Replaced with Problem", true)] public string Issues { get; set; } [EditorSequence("Issues", 1)] public ManagedProblem Problem { get; set; } static JobBillOfMaterialsItem() { LinkedProperties.Register(x => x.Product.DefaultInstance.Style, x => x.ID, x => x.Style.ID); LinkedProperties.Register(x => x.Product.DefaultInstance.Style, x => x.Code, x => x.Style.Code); LinkedProperties.Register(x => x.Product.DefaultInstance.Style, x => x.Description, x => x.Style.Description); LinkedProperties.Register(x => x.Product.DefaultInstance, x => x.NettCost, x => x.UnitCost); LinkedProperties.Register(x => x.Product.Supplier.SupplierLink, x => x.ID, x => x.Supplier.ID); LinkedProperties.Register(x => x.Job, x => x.DefaultScope.ID, x => x.Scope.ID); StockEntity.LinkStockDimensions(); } private bool bChanging; protected override void DoPropertyChanged(string name, object? before, object? after) { if (bChanging) return; try { bChanging = true; if (name.Equals(nameof(Quantity)) && after is double qty) TotalCost = UnitCost * qty; else if (name.Equals(nameof(UnitCost)) && after is double cost) TotalCost = cost * Quantity; else if (name.Equals(nameof(TotalCost)) && after is double total) { if (Quantity == 0) Quantity = 1; UnitCost = total / Quantity; } } finally { bChanging = false; } base.DoPropertyChanged(name, before, after); } public static void UpdateCosts(IEnumerable items, Dictionary changes) { void UpdateValue(JobBillOfMaterialsItem item,Expression> property, TType value) { CoreUtils.MonitorChanges( item, () => CoreUtils.SetPropertyValue(item, CoreUtils.GetFullPropertyName(property, "."), value), changes ); } var _items = items as JobBillOfMaterialsItem[] ?? items.ToArray(); var productids = _items.Where(x => x.Product.ID != Guid.Empty).Select(x => x.Product.ID).ToArray(); var jobids = _items.Select(x => x.Job.ID).ToList().Append(Guid.Empty).Distinct().ToArray(); if(productids.Length == 0) return; MultiQuery query = new MultiQuery(); query.Add( new Filter(x=>x.Product.ID).InList(productids), Columns.None().Add(x=>x.Product.ID) .Add(x=>x.SupplierLink.ID) .Add(x=>x.Style.ID) .AddDimensionsColumns(x => x.Dimensions) .Add(x=>x.Job.ID) .Add(x=>x.SupplierCode) .Add(x=>x.SupplierDescription) .Add(x=>x.TradePrice) .Add(x=>x.CostPrice) .Add(x=>x.DiscountGroup.ID) ); query.Add( new Filter(x=>x.Job.ID).InList(jobids) .And(x => x.Group.ID).InQuery( new Filter(x => x.Product.ID).InList(productids), x => x.DiscountGroup.ID ), Columns.None() .Add(x=>x.Group.ID) .Add(x=>x.Job.ID) .Add(x=>x.Group.Type) .Add(x=>x.Value) ); query.Add( new Filter(x=>x.Product.ID).InList(productids), Columns.None().Add(x=>x.Product.ID) .Add(x => x.Style.ID) .AddDimensionsColumns(x => x.Dimensions) .Add(x => x.NettCost) ); query.Query(); var supplierProducts = query.Get().ToArray(); var supplierDiscounts = query.Get().ToArray(); var productInstances = query.Get().ToArray(); foreach (var item in _items) { //Check Supplier / Job Specific Pricing var supplierProduct = supplierProducts.FirstOrDefault(x => x.Product.ID == item.Product.ID && x.SupplierLink.ID == item.Supplier.ID && x.Style.ID == item.Style.ID && x.Dimensions.Equals(item.Dimensions) && x.Job.ID == item.Job.ID); if (supplierProduct != null) { UpdateValue(item, x => x.UnitCost, supplierProduct.CostPrice); continue; } // Check Supplier / General Pricing supplierProduct = supplierProducts.FirstOrDefault(x => x.Product.ID == item.Product.ID && x.SupplierLink.ID == item.Supplier.ID && x.Style.ID == item.Style.ID && x.Dimensions.Equals(item.Dimensions) && x.Job.ID == Guid.Empty); if (supplierProduct != null) { var discount = supplierDiscounts.FirstOrDefault(x=>x.Job.ID == item.Job.ID && x.Group.ID == supplierProduct.DiscountGroup.ID); if (discount == null) discount = supplierDiscounts.FirstOrDefault(x=>x.Job.ID == Guid.Empty && x.Group.ID == supplierProduct.DiscountGroup.ID); if (discount != null) { if (discount.Group.Type == SupplierDiscountGroupType.Discount) { var disc = (100.0-discount?.Value ?? 0.00)/100.0; UpdateValue(item, x => x.UnitCost, supplierProduct.TradePrice * disc); } else UpdateValue(item, x => x.UnitCost, discount.Value); } else UpdateValue(item, x => x.UnitCost, supplierProduct.CostPrice); continue; } // Check Specific Product Instance var productInstance = productInstances.FirstOrDefault(x => x.Product.ID == item.Product.ID && x.Style.ID == item.Style.ID && x.Dimensions.Equals(item.Dimensions)); if (productInstance != null) UpdateValue(item, x => x.UnitCost, productInstance.NettCost); else UpdateValue(item, x => x.UnitCost, 0.0F); } } } }