using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using InABox.Core; using PRSClasses; namespace Comal.Classes { // public class StockHoldingUnitAggregate : CoreAggregate // { // public override Expression> Aggregate => x => x.Units; // // public override Dictionary>, Expression>> Links => // new Dictionary>, Expression>>() // { // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID }, // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID }, // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID }, // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID }, // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID }, // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity }, // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length }, // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width }, // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height }, // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight }, // }; // // public override AggregateCalculation Calculation => AggregateCalculation.Sum; // } // // public class StockHoldingValueAggregate : CoreAggregate // { // public override Expression> Aggregate => x => x.Value; // // public override Dictionary>, Expression>> Links => // new Dictionary>, Expression>>() // { // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID }, // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID }, // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID }, // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID }, // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID }, // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity }, // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length }, // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width }, // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height }, // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight }, // }; // // public override AggregateCalculation Calculation => AggregateCalculation.Sum; // } // // public class StockHoldingAverageValueFormula : IFormula // { // public Expression> Value => x => x.Value; // // public Expression>[] Modifiers => new Expression>[] { x => x.Units }; // // public FormulaOperator Operator => FormulaOperator.Divide; // // public FormulaType Type => FormulaType.Virtual; // } // // public class StockHoldingQuantityAggregate : CoreAggregate // { // public override Expression> Aggregate => x => x.Qty; // // public override Dictionary>, Expression>> Links => // new Dictionary>, Expression>>() // { // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID }, // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID }, // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID }, // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID }, // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID }, // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity }, // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length }, // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width }, // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height }, // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight }, // }; // // public override AggregateCalculation Calculation => AggregateCalculation.Sum; // } // public class StockHoldingAvailableAggregate : CoreAggregate // { // public override Expression> Aggregate => x => x.Units; // // public override Dictionary>, Expression>> Links => // new Dictionary>, Expression>>() // { // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID }, // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID }, // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID }, // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID }, // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID }, // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity }, // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length }, // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width }, // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height }, // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight }, // }; // // public override AggregateCalculation Calculation => AggregateCalculation.Sum; // // public override Filter? Filter => new Filter(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty); // } // public class StockHoldingWeightFormula : IFormula // { // public Expression> Value => x => x.Qty; // // public Expression>[] Modifiers => new Expression>[] { x => x.Dimensions.Weight }; // // public FormulaOperator Operator => FormulaOperator.Multiply; // // public FormulaType Type => FormulaType.Virtual; // } // // public class StockHoldingIsRemnantCondition : ICondition // { // public Expression> Left => x => x.Dimensions.Value; // // public Condition Condition => Condition.LessThan; // // public Expression> Right => x => x.Product.DefaultInstance.Dimensions.Value; // // public Expression> True => x => true; // // public Expression> False => x => null; // // public ConditionType Type => ConditionType.Virtual; // } // public class StockHoldingUnionGenerator : AutoEntityUnionGenerator // { // protected override void Configure() // { // AddTable(); // } // // public override bool Distinct => true; // // public override Column[] IDColumns => Columns; // // public static Column[] Columns => new Column[] // { // new Column(x => x.Job.ID), // new Column(x => x.Location.ID), // new Column(x => x.Product.ID), // new Column(x => x.Style.ID), // new Column(x => x.Dimensions.Unit.ID), // new Column(x => x.Dimensions.Quantity), // new Column(x => x.Dimensions.Length), // new Column(x => x.Dimensions.Width), // new Column(x => x.Dimensions.Height), // new Column(x => x.Dimensions.Weight), // }; // // public static Filter? GetFilter(IStockHolding holding) // { // var filter = new Filters(); // // foreach(var column in Columns) // { // filter.Add(new Filter(column.Cast()).IsEqualTo(CoreUtils.GetPropertyValue(holding, column.Property))); // } // // return filter.Combine(); // } // } // // [UserTracking(typeof(StockMovement))] // [AutoEntity(typeof(StockHoldingUnionGenerator))] // public class StockHoldingView : StockEntity, IRemotable, IPersistent, IOneToMany, IOneToMany, // IStockHolding, ILicense // { // // public override ProductLink Product { get; set; } // // public ProductStyleLink Style { get; set; } // // public StockLocationLink Location { get; set; } // // public JobLink Job { get; set; } // // [RequiredColumn] // [DimensionsEditor(typeof(StockDimensions), AllowEditingUnit = false)] // public override StockDimensions Dimensions { get; set; } // // [Condition(typeof(StockHoldingIsRemnantCondition))] // [NullEditor] // public bool IsRemnant { get; set; } // // [Aggregate(typeof(StockHoldingUnitAggregate))] // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] // public double Units { get; set; } // // [Aggregate(typeof(StockHoldingQuantityAggregate))] // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] // public double Qty { get; set; } // // [Formula(typeof(StockHoldingWeightFormula))] // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] // public double Weight { get; set; } // // [Aggregate(typeof(StockHoldingValueAggregate))] // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] // public double Value { get; set; } // // [Formula(typeof(StockHoldingAverageValueFormula))] // [DoubleEditor(Editable = Editable.Disabled)] // public double AverageValue { get; set; } // } // FV 6/2/24: This used to be a view (see above), but we were running into performance issues trying to // get aggregates fof stock locations (ie show me how many holdings in a given location was taking upwrds of // 20 seconds. Have moved update logic to StockHoldingStore / StockMovementStore so updates are a bit slower, // but reads are now much faster. [UserTracking(typeof(StockMovement))] [Unrecoverable] public class StockHolding : StockEntity, IRemotable, IPersistent, IOneToMany, IOneToMany, IStockHolding, ILicense { [Editable(Editable.Disabled)] [EditorSequence(1)] public StockLocationLink Location { get; set; } private class ProductLookupGenerator : LookupDefinitionGenerator { public override Filter? DefineFilter(StockHolding[] items) => LookupFactory.DefineFilter().And(x => x.NonStock).IsEqualTo(false); } [Editable(Editable.Disabled)] [EditorSequence(2)] [LookupDefinition(typeof(ProductLookupGenerator))] public override ProductLink Product { get; set; } [DimensionsEditor(typeof(StockDimensions), AllowEditingUnit = false)] [Editable(Editable.Disabled)] [EditorSequence(3)] public override StockDimensions Dimensions { get; set; } [Editable(Editable.Disabled)] [EditorSequence(4)] public ProductStyleLink Style { get; set; } [Editable(Editable.Disabled)] [EditorSequence(4)] public JobLink Job { get; set; } [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] [EditorSequence(5)] public double Units { get; set; } [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] [EditorSequence(6)] public double Qty { get; set; } [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] [EditorSequence(7)] public double Weight { get; set; } [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] [EditorSequence(8)] public double Value { get; set; } [DoubleEditor(Editable = Editable.Disabled)] [EditorSequence(9)] public double AverageValue { get; set; } [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] [EditorSequence(10)] public double Available { get; set; } public static Column[] Columns => new Column[] { new Column(x => x.Job.ID), new Column(x => x.Location.ID), new Column(x => x.Product.ID), new Column(x => x.Style.ID), new Column(x => x.Dimensions.Unit.ID), new Column(x => x.Dimensions.Quantity), new Column(x => x.Dimensions.Length), new Column(x => x.Dimensions.Width), new Column(x => x.Dimensions.Height), new Column(x => x.Dimensions.Weight), }; public static Filter? GetFilter(IStockHolding holding) { var filter = new Filters(); foreach(var column in Columns) { filter.Add(new Filter(column.Cast()).IsEqualTo(CoreUtils.GetPropertyValue(holding, column.Property))); } return filter.Combine(); } } public static class StockHoldingExtensions { /// /// Create a new stock movement from an , copying across the "key" properties; /// that is, the job, product, style, location and dimensions. /// /// /// public static StockMovement CreateMovement(this IStockHolding holding) { var movement = new StockMovement(); movement.Job.ID = holding.Job.ID; movement.Job.Synchronise(holding.Job); movement.Product.ID = holding.Product.ID; movement.Product.Synchronise(holding.Product); movement.Style.ID = holding.Style.ID; movement.Style.Synchronise(holding.Style); movement.Location.ID = holding.Location.ID; movement.Location.Synchronise(holding.Location); movement.Dimensions.CopyFrom(holding.Dimensions); return movement; } public static IEnumerable GroupMovements(IEnumerable movements) { var grouped = new List(); var toGroup = movements.AsList(); while (toGroup.Count > 0) { var first = toGroup.First(); var selected = toGroup.Where(x => x.IsEqualTo(first)).ToList(); var holding = grouped.FirstOrDefault(x => x.IsEqualTo(first)); if (holding == null) { holding = new StockHolding(); holding.Location.ID = first.Location.ID; holding.Product.ID = first.Product.ID; holding.Style.ID = first.Style.ID; holding.Job.ID = first.Job.ID; holding.Dimensions.CopyFrom(first.Dimensions); } holding.Recalculate(selected); toGroup.RemoveAll(x => selected.Any(s => s.ID == x.ID)); } return grouped; } public static bool IsEqualTo(this IStockHolding h1, IStockHolding h2) { return h1.Product.ID == h2.Product.ID && h1.Location.ID == h2.Location.ID && h1.Job.ID == h2.Job.ID && h1.Style.ID == h2.Style.ID && h1.Dimensions.Unit.ID == h2.Dimensions.Unit.ID && h1.Dimensions.Length.IsEffectivelyEqual(h2.Dimensions.Length) && h1.Dimensions.Width.IsEffectivelyEqual(h2.Dimensions.Width) && h1.Dimensions.Height.IsEffectivelyEqual(h2.Dimensions.Height) && h1.Dimensions.Quantity.IsEffectivelyEqual(h2.Dimensions.Quantity) && h1.Dimensions.Weight.IsEffectivelyEqual(h2.Dimensions.Weight); } public static void Recalculate(this StockHolding holding, IEnumerable movements) { movements = movements.AsIList(); var units = movements.Sum(x => x.Units); var cost = movements.Select(x => x.Units * x.Cost).Sum(); var available = movements.Where(x => x.JobRequisitionItem.ID == Guid.Empty).Sum(x => x.Units); holding.Units = units; holding.Available = available; holding.Qty = movements.Sum(x => x.Units * x.Dimensions.Value); holding.Value = cost; holding.AverageValue = units.IsEffectivelyEqual(0.0F) ? 0.0d : cost / units; holding.Weight = holding.Qty * holding.Dimensions.Weight; } } }