using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using InABox.Clients; using InABox.Core; using PRSClasses; namespace Comal.Classes { public class StockHoldingLastStocktake : CoreAggregate { public override Expression> Aggregate => x => x.Date; public override Filter Filter => new Filter(x => x.Type) .IsEqualTo(StockMovementType.StockTake); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID }, { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID }, { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID }, { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID }, }.AddRange(Dimensions.GetLinks(x => x.Dimensions, x => x.Dimensions)); public override AggregateCalculation Calculation => AggregateCalculation.Maximum; } [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))] [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; } [Formula(typeof(StockHoldingAllocatedFormula))] [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)] [EditorSequence(11)] public double Allocated { get; set; } [Aggregate(typeof(StockHoldingLastStocktake))] [DateEditor(Editable = Editable.Disabled)] [EditorSequence(11)] public DateTime LastStockTake { 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(); } } internal class StockHoldingAllocatedFormula : IFormula { public Expression> Value => x => x.Qty; public FormulaOperator Operator => FormulaOperator.Subtract; public Expression>[] Modifiers => new Expression>[] { x => x.Available }; public FormulaType Type => FormulaType.Virtual; } 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. /// /// /// Also sets the to today. /// /// /// public static StockMovement CreateMovement(this IStockHolding holding) { var movement = new StockMovement(); movement.Date = DateTime.Now; 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 List 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.CopyFrom(first.Location); holding.Product.CopyFrom(first.Product); holding.Style.CopyFrom(first.Style); holding.Job.CopyFrom(first.Job); holding.Dimensions.CopyFrom(first.Dimensions); grouped.Add(holding); } holding.Recalculate(selected); toGroup.RemoveAll(x => selected.Any(s => s.ID == x.ID)); } return grouped; } public static bool IsEqualTo(this IStockHolding row1, IStockHolding row2) { return row1.Product.ID == row2.Product.ID && row1.Location.ID == row2.Location.ID && row1.Job.ID == row2.Job.ID && row1.Style.ID == row2.Style.ID && row1.Dimensions.Unit.ID == row2.Dimensions.Unit.ID && row1.Dimensions.Length.IsEffectivelyEqual(row2.Dimensions.Length) && row1.Dimensions.Width.IsEffectivelyEqual(row2.Dimensions.Width) && row1.Dimensions.Height.IsEffectivelyEqual(row2.Dimensions.Height) && row1.Dimensions.Quantity.IsEffectivelyEqual(row2.Dimensions.Quantity) && row1.Dimensions.Weight.IsEffectivelyEqual(row2.Dimensions.Weight); } public static bool IsEqualTo(this CoreRow row1, CoreRow row2) where T1 : IStockHolding where T2 : IStockHolding { return row1.Get(x=>x.Product.ID) == row2.Get(x => x.Product.ID) && row1.Get(x=>x.Location.ID) == row2.Get(x => x.Location.ID) && row1.Get(x=>x.Job.ID) == row2.Get(x => x.Job.ID) && row1.Get(x=>x.Style.ID) == row2.Get(x => x.Style.ID) && row1.Get(x=>x.Dimensions.Unit.ID) == row2.Get(x => x.Dimensions.Unit.ID) && row1.Get(x=>x.Dimensions.Length).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Length)) && row1.Get(x=>x.Dimensions.Width).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Width)) && row1.Get(x=>x.Dimensions.Height).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Height)) && row1.Get(x=>x.Dimensions.Quantity).IsEffectivelyEqual(row2.Get(x=>x.Dimensions.Quantity)) && row1.Get(x=>x.Dimensions.Weight).IsEffectivelyEqual(row2.Get(x=>x.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; } public static IEnumerable LoadRequisitionItems(this StockHolding holding, bool alwaysshowunallocated = false, Columns? columns = null) { columns ??= Columns.None(); columns.Add(x => x.ID) .Add(x => x.Product.ID) .Add(x => x.Style.ID) .AddDimensionsColumns(x => x.Dimensions) .Add(x => x.Job.ID) .Add(x => x.Job.JobNumber) .Add(x => x.Job.Name) .Add(x => x.Requisition.Number) .Add(x => x.Requisition.Description) .Add(x => x.Qty); var items = new Client().Query( new Filter(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID), columns) .ToObjects() .Where(x=>x.Product.ID == holding.Product.ID && x.Style.ID == holding.Style.ID && x.Dimensions.Equals(holding.Dimensions)); if (holding.Available > 0 || alwaysshowunallocated) { var requi = new JobRequisitionItem() { Qty = holding.Available }; requi.Requisition.Description = "Unallocated Items"; items = CoreUtils.One(requi).Concat(items); } return items; } public static IEnumerable AdjustValue(this StockHolding holding, double unitvalue, StockMovementBatch batch) { List _result = new List(); var movements = Client.Query( new Filter(x => x.Location.ID).IsEqualTo(holding.Location.ID) .And(x => x.Style.ID).IsEqualTo(holding.Style.ID) .And(x => x.Dimensions).DimensionEquals(holding.Dimensions) .And(x => x.Job.ID).IsEqualTo(holding.Job.ID), Columns.Required().Add(x=>x.Units) ).Rows.ToObjects().ToArray(); var _allocations = movements.GroupBy(x => x.JobRequisitionItem.ID); foreach (var _allocation in _allocations) { var _units = _allocation.Sum(x => x.Units); if (!_units.IsEffectivelyEqual(0.0)) { var _transout = holding.CreateMovement(); _transout.Employee.ID = batch.Employee.ID; _transout.Issued = _units; _transout.Cost = holding.AverageValue; _transout.Type = StockMovementType.TransferOut; _transout.JobRequisitionItem.ID = _allocation.Key; _transout.Batch.ID = batch.ID; _transout.Notes = $"Adjusting Average Value from ${holding.AverageValue:F2} to ${unitvalue:F2}"; _result.Add(_transout); var _transin = holding.CreateMovement(); _transin.Date = _transout.Date.AddTicks(1); _transout.Employee.ID = batch.Employee.ID; _transin.Received = _units; _transin.Cost = unitvalue; _transin.Type = StockMovementType.TransferIn; _transin.Transaction = _transout.Transaction; _transin.JobRequisitionItem.ID = _allocation.Key; _transin.Batch.ID = batch.ID; _transin.Notes = $"Adjusting Average Value from ${holding.AverageValue:F2} to ${unitvalue:F2}"; _result.Add(_transin); } } return _result; } // public static IEnumerable> GetAllocations(this StockHolding holding, bool alwaysshowunallocated) // { // var table = new Client().Query( // StockHolding.GetFilter(holding), // new Columns(x => x.Units) // .Add(x => x.Location.ID) // .Add(x => x.Product.ID) // .Add(x => x.Style.ID) // .AddDimensionsColumns(x => x.Dimensions) // .Add(x => x.Cost) // .Add(x => x.OrderItem.ID) // .Add(x => x.JobRequisitionItem.ID) // ); // // var movements = table // .ToObjects(); // // var groups = movements // .GroupBy(x => new // { // Location = x.Location.ID, // Product = x.Product.ID, // Style = x.Style.ID, // x.Dimensions, // x.Cost, // OrderItem = x.OrderItem.ID, // JobRequisitionItem = x.JobRequisitionItem.ID // }); // // var result = groups // .Select(x => new Tuple( // x.Key.JobRequisitionItem, // x.Sum(x => x.Units)) // ).ToList(); // // if (alwaysshowunallocated || !holding.Available.IsEffectivelyEqual(0)) // result.Add(new Tuple(Guid.Empty,holding.Available)); // // return result; // // } } }