using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using InABox.Clients; using InABox.Core; using PRSClasses; namespace Comal.Classes { public enum JobRequisitionItemStatus { NotChecked, // Default /// /// All required stock has been received, and is in the correct . /// /// /// This can be set even if there are unreceived , /// since if we got the stock some other way, we still think of it as allocated. /// Allocated, /// /// All required stock has been received, but some is not in the correct , meaning a treatment is required. /// /// /// This can be set even if there are unreceived , /// since if we got the stock some other way, we still think of it as having the stock allocated. /// TreatmentRequired, /// /// The has been set, but there are no s for /// this . /// OrderRequired, /// /// We don't yet have all the stock, and there is at least one unreceived of type /// . /// OnOrder, /// /// We don't yet have all the stock, and there is at least one unreceived of type /// and none of type . /// TreatmentOnOrder, [Obsolete] Received,// Drop [Obsolete] TreatmentReceived,// Drop /// /// The has been cancelled, meaning it has a non-empty . /// Cancelled, /// /// The has been archived, meaning it has a non-empty . /// Archived, /// /// The has been issued, meaning that it has been allocated, and there are stock movements of type adding up to the correct total. /// Issued } public class JobRequisitionItemTotalQtyFormula : IFormula { public Expression> Value => x => x.Qty; public Expression>[] Modifiers => new Expression>[] { x => x.Dimensions.Value }; public FormulaOperator Operator => FormulaOperator.Multiply; public FormulaType Type => FormulaType.Virtual; } public class JobRequisitionItemInStockAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Units; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>> { { x => x.JobRequisitionItem.ID, x => x.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class JobRequisitionItemOrdersAggregate : CoreAggregate { public override Expression> Aggregate => x => x.PurchaseOrderItem.Qty; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>> { { x => x.JobRequisitionItem.ID, x => x.ID }, }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; public override Filter? Filter => new Filter(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(null); } public class JobRequisitionItemOnOrderAggregate : CoreAggregate { public override Expression> Aggregate => x => x.PurchaseOrderItem.Qty; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>> { { x => x.JobRequisitionItem.ID, x => x.ID }, { x => x.PurchaseOrderItem.Product.ID, x => x.Product.ID }, }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; public override Filter? Filter => new Filter(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(null); } public class JobRequisitionItemAllocatedAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Units; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>> { { x => x.JobRequisitionItem.ID, x => x.ID }, { x => x.Style.ID, x => x.Style.ID }, }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class JobRequisitionItemTreatmentOnOrderFormula : IFormula { public Expression> Value => x => x.TotalOrders; public Expression>[] Modifiers => new Expression>[] { x => x.OnOrder }; public FormulaOperator Operator => FormulaOperator.Subtract; public FormulaType Type => FormulaType.Virtual; } public class JobRequisitionItemTreatmentRequiredFormula : IFormula { public Expression> Value => x => x.InStock; public Expression>[] Modifiers => new Expression>[] { x => x.Allocated }; public FormulaOperator Operator => FormulaOperator.Subtract; public FormulaType Type => FormulaType.Virtual; } public class JobRequisitionItemIssuedAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Issued; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>> { { x => x.JobRequisitionItem.ID, x => x.ID }, { x => x.Style.ID, x => x.Style.ID }, }; public override Filter? Filter => new Filter(x => x.Type).IsEqualTo(StockMovementType.Issue); public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public interface IJobRequisitionItem : IEntity { } [Caption("Items")] [UserTracking(typeof(Job))] public class JobRequisitionItem : StockEntity, IRemotable, IPersistent, IOneToMany, ILicense, IJobMaterial, ISequenceable, IIssues, IJobRequisitionItem { [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; } [DoubleEditor(Editable = Editable.Hidden)] [Formula(typeof(JobRequisitionItemTotalQtyFormula))] 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; } [EnumLookupEditor(typeof(JobRequisitionItemStatus), Editable = Editable.Disabled)] [EditorSequence(9)] [LoggableProperty] [RequiredColumn] public JobRequisitionItemStatus Status { get; set; } = JobRequisitionItemStatus.NotChecked; /// /// The amount of this requisition item that is currently in stock, which is an aggregate of the property. /// [Aggregate(typeof(JobRequisitionItemInStockAggregate))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(10)] public double InStock { get; set; } [Aggregate(typeof(JobRequisitionItemOrdersAggregate))] [DoubleEditor(Editable = Editable.Disabled, Visible = Visible.Optional)] [EditorSequence(11)] public double TotalOrders { get; set; } [Aggregate(typeof(JobRequisitionItemOnOrderAggregate))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(12)] public double OnOrder { get; set; } [Formula(typeof(JobRequisitionItemTreatmentRequiredFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(13)] public double TreatmentRequired { get; set; } [Formula(typeof(JobRequisitionItemTreatmentOnOrderFormula))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(14)] public double TreatmentOnOrder { get; set; } [Aggregate(typeof(JobRequisitionItemAllocatedAggregate))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(15)] public double Allocated { get; set; } [Aggregate(typeof(JobRequisitionItemIssuedAggregate))] [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(16)] public double Issued { get; set; } [EntityRelationship(DeleteAction.SetNull)] [RequiredColumn] [Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")] [NullEditor] public PurchaseOrderItemLink PurchaseOrderItem { get; set; } [Aggregate(typeof(JobRequisitionItemPurchaseOrderNumberAggregate))] [TextBoxEditor(Editable = Editable.Hidden)] public string PurchaseOrderNumbers { get; set; } [RequiredColumn] [EditorSequence(17)] public DateTime Cancelled { get; set; } = DateTime.MinValue; [RequiredColumn] [EditorSequence(18)] public DateTime Archived { get; set; } = DateTime.MinValue; [RequiredColumn] [Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")] [NullEditor] public DateTime Ordered { get; set; } = DateTime.MinValue; [RequiredColumn] [EditorSequence(19)] public DateTime OrderRequired { get; set; } = DateTime.MinValue; [NullEditor] public string Issues { 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 static void UpdateCosts(IEnumerable items, Dictionary changes) { 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(); 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, Classes.Dimensions.ColumnsType.Local) .Add(x=>x.Job.ID) .Add(x=>x.SupplierCode) .Add(x=>x.SupplierDescription) .Add(x=>x.CostPrice) ); 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(); var supplierProducts = 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 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 = 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); } } } public class JobRequisitionItemPurchaseOrderNumberAggregate : CoreAggregate { public override Expression> Aggregate => x => x.PurchaseOrderItem.PurchaseOrderLink.PONumber; public override Dictionary>, Expression>> Links { get; } = new Dictionary>, Expression>> { { x => x.JobRequisitionItem.ID, x => x.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Concat; } }