using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using Comal.Classes.SecurityDescriptors; using InABox.Clients; using InABox.Core; using PRSClasses; namespace Comal.Classes { public enum JobRequisitionItemStatus { Stock, Treatment, Allocated, Issued, Cancelled, Archived } public interface IJobRequisitionItem : IEntity { public DateTime Cancelled { get; set; } } [Caption("Items")] [UserTracking(typeof(Job))] public class JobRequisitionItem : StockEntity, IRemotable, IPersistent, IOneToMany, ILicense, IJobMaterial, ISequenceable, IIssues, IJobRequisitionItem, IProblems { [EntityRelationship(DeleteAction.Cascade)] [Editable(Editable.Hidden)] public JobLink Job { get; set; } [EntityRelationship(DeleteAction.Cascade)] [Editable(Editable.Hidden)] public JobRequisitionLink Requisition { get; set; } [EntityRelationship(DeleteAction.SetNull)] [EditorSequence(1)] [RequiredColumn] public override ProductLink Product { get; set; } [EditorSequence(2)] [RequiredColumn] public ProductStyleLink Style { get; set; } [NullEditor] [Obsolete("Replaced with Dimensions", true)] public double UnitSize { get; set; } [EditorSequence(3)] [RequiredColumn] [DimensionsEditor(typeof(StockDimensions))] public override StockDimensions Dimensions { get; set; } [EditorSequence(4)] [RequiredColumn] public double Qty { get; set; } private class TotalQtyFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula(FormulaOperator.Multiply, Property(x => x.Qty), Property(x => x.Dimensions.Value)); } [DoubleEditor(Editable = Editable.Hidden)] [ComplexFormula(typeof(TotalQtyFormula))] public double TotalQty { get; set; } [EditorSequence(5)] public double UnitCost { get; set; } [EditorSequence(6)] [CurrencyEditor(Summary = Summary.Sum)] public double TotalCost { get; set; } [EditorSequence(7)] [MemoEditor] public string Notes { get; set; } [EditorSequence(8)] public SupplierLink Supplier { get; set; } private class StatusFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( Property(x => x.Cancelled), Condition.NotEqual, Constant(DateTime.MinValue), DateTime.MinValue ).Then( Constant(JobRequisitionItemStatus.Cancelled) ).Else( If( Property(x => x.Archived), Condition.NotEqual, Constant(DateTime.MinValue), DateTime.MinValue ).Then( Constant(JobRequisitionItemStatus.Archived) ).Else( If( Property(x => x.Issued), Condition.GreaterThanOrEqualTo, Property(x => x.Qty) ).Then( Constant(JobRequisitionItemStatus.Issued) ).Else( If( Formula( FormulaOperator.Add, Property(x => x.Issued), Property(x => x.Allocated) ), Condition.GreaterThanOrEqualTo, Property(x => x.Qty) ).Then( Constant(JobRequisitionItemStatus.Allocated) ).Else( If( Formula( FormulaOperator.Add, Property(x => x.InStock), Property(x => x.Issued) ), Condition.GreaterThanOrEqualTo, Property(x => x.Qty) ).Then( Constant(JobRequisitionItemStatus.Treatment) ).Else( Constant(JobRequisitionItemStatus.Stock) ) ) ) ) ); } [ComplexFormula(typeof(StatusFormula))] [EditorSequence(8)] public JobRequisitionItemStatus Status { get; set; } private class InStockFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ).Then( Aggregate(AggregateCalculation.Sum, x => x.Property(x => x.Units)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) ).Else( Formula(FormulaOperator.Divide, Aggregate(AggregateCalculation.Sum, x => x.Property(x => x.Units)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID), Property(x => x.Dimensions.Value) ) ); } /// /// The amount of this requisition item that is currently in stock, which is an aggregate of the property. /// [ComplexFormula(typeof(InStockFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(10)] public double InStock { get; set; } private class OnOrderFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ).Then( Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter(x => x.Item.ReceivedDate).IsEqualTo(null)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) .WithLink(x => x.Item.Product.ID, x => x.Product.ID) ).Else( Formula(FormulaOperator.Divide, Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter(x => x.Item.ReceivedDate).IsEqualTo(null)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) .WithLink(x => x.Item.Product.ID, x => x.Product.ID), Property(x => x.Dimensions.Value) ) ); } [ComplexFormula(typeof(OnOrderFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(11)] public double OnOrder { get; set; } private class GeneralOrderFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula( FormulaOperator.Add, Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Unallocated), new Filter(x => x.ReceivedDate).IsEqualTo(null) .And(x => x.Job.ID).IsEqualTo(Guid.Empty) ) .WithLink(x => x.Product.ID, x => x.Product.ID), If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ) .Then( Aggregate( AggregateCalculation.Sum, x => x.Property(c => c.Quantity), new Filter(x => x.Item.ReceivedDate).IsEqualTo(null) .And(x => x.Job.ID).IsEqualTo(Guid.Empty) .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty) ).WithLink(x => x.Item.Product.ID, x => x.Product.ID) ) .Else( Formula( FormulaOperator.Divide, Aggregate( AggregateCalculation.Sum, x => x.Property(c => c.Quantity), new Filter(x => x.Item.ReceivedDate).IsEqualTo(null) .And(x => x.Job.ID).IsEqualTo(Guid.Empty) .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty) ).WithLink(x => x.Item.Product.ID, x => x.Product.ID), Property(x => x.Dimensions.Value) ) ) ); } [ComplexFormula(typeof(GeneralOrderFormula))] [DoubleEditor(Editable = Editable.Hidden)] public double GeneralOrder { get; set; } private class TotalOrdersFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ).Then( Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter(x => x.Item.ReceivedDate).IsEqualTo(null)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) ).Else( Formula(FormulaOperator.Divide, Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter(x => x.Item.ReceivedDate).IsEqualTo(null)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID), Property(x => x.Dimensions.Value) ) ); } [ComplexFormula(typeof(TotalOrdersFormula))] [DoubleEditor(Editable = Editable.Disabled, Visible = Visible.Optional)] [EditorSequence(12)] public double TotalOrders { get; set; } private class TreatmentRequiredFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula( FormulaOperator.Subtract, Property(x => x.InStock), Property(x => x.Allocated), Formula( FormulaOperator.Subtract, Property(x => x.TotalOrders), Property(x => x.OnOrder) ) ); } [ComplexFormula(typeof(TreatmentRequiredFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(13)] public double TreatmentRequired { get; set; } private class TreatmentOnOrderFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula(FormulaOperator.Subtract, Property(x => x.TotalOrders), Property(x => x.OnOrder)); } [ComplexFormula(typeof(TreatmentOnOrderFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(14)] public double TreatmentOnOrder { get; set; } private class AllocatedFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula( FormulaOperator.Subtract, Formula( FormulaOperator.Add, If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ).Then( Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Units)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) .WithLink(x => x.Style.ID, x => x.Style.ID) ).Else( Formula(FormulaOperator.Divide, Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Units)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) .WithLink(x => x.Style.ID, x => x.Style.ID), Property(x => x.Dimensions.Value) ) ), Property(x => x.OnOrder) ), Property(x => x.TotalOrders) ); } [ComplexFormula(typeof(AllocatedFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(15)] public double Allocated { get; set; } private class PickRequestedFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ).Then( Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter(x => x.RequisitionLink.StockUpdated).IsEqualTo( DateTime.MinValue) ) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) ).Else( Formula(FormulaOperator.Divide, Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter(x => x.RequisitionLink.StockUpdated).IsEqualTo( DateTime.MinValue) ) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID), Property(x => x.Dimensions.Value) ) ); } [ComplexFormula(typeof(PickRequestedFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(16)] public double PickRequested { get; set; } private class IssuedFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( Property(x => x.Dimensions.Unit.Conversion), Condition.Equals, Constant(""), "" ).Then( Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Issued), new Filter(x => x.Type).IsEqualTo(StockMovementType.Issue)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) .WithLink(x => x.Style.ID, x => x.Style.ID) ).Else( Formula(FormulaOperator.Divide, Aggregate( AggregateCalculation.Sum, x => x.Property(x => x.Issued), new Filter(x => x.Type).IsEqualTo(StockMovementType.Issue)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID) .WithLink(x => x.Style.ID, x => x.Style.ID), Property(x => x.Dimensions.Value) ) ); } [ComplexFormula(typeof(IssuedFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(17)] public double Issued { get; set; } private class TotalIssuedFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula(FormulaOperator.Multiply, Property(x => x.Issued), Property(x => x.Dimensions.Value)); } [DoubleEditor(Editable = Editable.Hidden)] [ComplexFormula(typeof(TotalIssuedFormula))] public double TotalIssued { get; set; } private class TotalAllocatedFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula(FormulaOperator.Multiply, Property(x => x.Allocated), Property(x => x.Dimensions.Value)); } [DoubleEditor(Editable = Editable.Hidden)] [ComplexFormula(typeof(TotalAllocatedFormula))] public double TotalAllocated { get; set; } [EntityRelationship(DeleteAction.SetNull)] [RequiredColumn] [Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")] [NullEditor] public PurchaseOrderItemLink PurchaseOrderItem { get; set; } private class PurchaseOrderNumbersFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Aggregate( AggregateCalculation.Concat, x => x.Property(x => x.Item.PurchaseOrderLink.PONumber)) .WithLink(x => x.JobRequisitionItem.ID, x => x.ID); } [ComplexFormula(typeof(PurchaseOrderNumbersFormula))] [TextBoxEditor(Editable = Editable.Hidden)] public string PurchaseOrderNumbers { get; set; } [RequiredColumn] [EditorSequence(18)] public DateTime Cancelled { get; set; } = DateTime.MinValue; [RequiredColumn] [EditorSequence(19)] public DateTime Archived { get; set; } = DateTime.MinValue; [RequiredColumn] [Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")] [NullEditor] public DateTime Ordered { get; set; } = DateTime.MinValue; [RequiredColumn] [EditorSequence(20)] public DateTime OrderRequired { get; set; } = DateTime.MinValue; [NullEditor] [Obsolete("Replaced with Problem", true)] public string Issues { get; set; } [EditorSequence("Issues", 1)] public ManagedProblem Problem { get; set; } [NullEditor] public long Sequence { get; set; } static JobRequisitionItem() { 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); 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(Qty)) && after is double qty) TotalCost = UnitCost * qty; else if (name.Equals(nameof(UnitCost)) && after is double cost) TotalCost = cost * Qty; else if (name.Equals(nameof(TotalCost)) && after is double total) { if (Qty == 0) Qty = 1; UnitCost = total / Qty; } } finally { bChanging = false; } base.DoPropertyChanged(name, before, after); } public class JRICostData { public Guid[] ProductIDs { get; set; } = Array.Empty(); public Guid[] SupplierIDs { get; set; } = Array.Empty(); public ProductInstance[] Instances { get; set; } = Array.Empty(); public SupplierProduct[] SupplierProducts { get; set; } = Array.Empty(); } public static void UpdateCosts(IEnumerable items, Dictionary changes, JRICostData? data = null) { var itemList = items.AsIList(); void UpdateValue(JobRequisitionItem item,Expression> property, TType value) { CoreUtils.MonitorChanges( item, () => CoreUtils.SetPropertyValue(item, CoreUtils.GetFullPropertyName(property, "."), value), changes ); } var productIDs = items.Where(x => x.Product.ID != Guid.Empty).Select(x => x.Product.ID).ToArray(); if(productIDs.Length == 0) { return; } var supplierIDs = items.Where(x => x.Supplier.ID != Guid.Empty).Select(x => x.Supplier.ID).ToArray(); ProductInstance[] instances; SupplierProduct[] supplierProducts; var needProducts = data is null || productIDs.Any(x => !data.ProductIDs.Contains(x)); var needSuppliers = data is null || supplierIDs.Any(x => !data!.SupplierIDs.Contains(x)); if(needProducts || needSuppliers) { MultiQuery query = new MultiQuery(); query.Add( new Filter(x=>x.Product.ID).InList(productIDs).And(x => x.SupplierLink.ID).InList(supplierIDs), Columns.None().Add(x=>x.Product.ID) .Add(x=>x.SupplierLink.ID) .Add(x=>x.Style.ID) .AddDimensionsColumns(x => x.Dimensions, Classes.Dimensions.ColumnsType.Local) .Add(x=>x.Job.ID) .Add(x=>x.SupplierCode) .Add(x=>x.SupplierDescription) .Add(x=>x.CostPrice) ); if(needProducts) { 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, Classes.Dimensions.ColumnsType.Local) .Add(x => x.NettCost) ); } query.Query(); supplierProducts = query.Get().ToArray(); if (needProducts) { instances = query.Get().ToArray(); } else { instances = data!.Instances; } if(data != null) { data.SupplierIDs = supplierIDs; data.SupplierProducts = supplierProducts; if (needProducts) { data.ProductIDs = productIDs; data.Instances = instances; } } } else { supplierProducts = data!.SupplierProducts; instances = data!.Instances; } foreach (var item in items) { if(supplierProducts != null) { //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 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) { UpdateValue(item, x => x.UnitCost, supplierProduct.CostPrice); continue; } } // Check Specific Product Instance var productInstance = instances.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); } } } }