using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using InABox.Core; using PRSClasses; namespace Comal.Classes { public class StockMovementDocumentCount : CoreAggregate { public override Expression> Aggregate => x => x.ID; public override AggregateCalculation Calculation => AggregateCalculation.Count; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovementBatchDocument => StockMovementBatchDocument.EntityLink.ID, StockMovement => StockMovement.Batch.ID } }; } public class StockMovementLink : EntityLink { [NullEditor] public override Guid ID { get; set; } } public class StockMovementValueFormula : IFormula { public Expression> Value => x => x.Units; public Expression>[] Modifiers => new Expression>[] { x => x.Cost }; public FormulaOperator Operator => FormulaOperator.Multiply; public FormulaType Type => FormulaType.Virtual; } public class StockMovementQuantityFormula : IFormula { public Expression> Value => x => x.Units; public Expression>[] Modifiers => new Expression>[] { x => x.Dimensions.Value }; public FormulaOperator Operator => FormulaOperator.Multiply; public FormulaType Type => FormulaType.Virtual; } [UserTracking("Warehousing")] public class StockMovement : StockEntity, IRemotable, IPersistent, IOneToMany, IOneToMany, ILicense, IStockHolding, IJobMaterial, IExportable, IImportable, IPostable { [DateTimeEditor] [EditorSequence(0)] [SecondaryIndex] public DateTime Date { get; set; } private class ProductLookupGenerator : LookupDefinitionGenerator { public override Filter? DefineFilter(StockMovement[] items) => LookupFactory.DefineFilter().And(x => x.NonStock).IsEqualTo(false); } [EditorSequence(1)] [EntityRelationship(DeleteAction.Cascade)] [RequiredColumn] [LookupDefinition(typeof(ProductLookupGenerator))] public override ProductLink Product { get; set; } [EditorSequence(2)] [RequiredColumn] [DimensionsEditor(typeof(StockDimensions))] public override StockDimensions Dimensions { get; set; } [EditorSequence(3)] [EntityRelationship(DeleteAction.SetNull)] public ProductStyleLink Style { get; set; } // Allowed to be negative. [DoubleEditor(Summary = Summary.Sum)] [EditorSequence(4)] public double Received { get; set; } [DoubleEditor(Summary = Summary.Sum)] [EditorSequence(5)] public double Issued { get; set; } /// /// This indicates the balance of the holding at the current point in time for a stock movement, populated for stock takes. /// [DoubleEditor] [EditorSequence(6)] public double Balance { get; set; } private class StockMovementUnitsFormula : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => Formula(FormulaOperator.Subtract, Property(x => x.Received), Property(x => x.Issued)); } [ComplexFormula(typeof(StockMovementUnitsFormula))] [EditorSequence(7)] [DoubleEditor(Visible=Visible.Optional, Editable = Editable.Hidden, Summary= Summary.Sum)] /// /// Units = Received - Issued /// public double Units { get; set; } private class IsRemnantCondition : ComplexFormulaGenerator { public override IComplexFormulaNode GetFormula() => If( x => x.Property(x => x.Dimensions.Value), Condition.LessThan, x => x.Property(x => x.Product.DefaultInstance.Dimensions.Value)) .Then(Constant(true)) .Else(Constant(false)); } // IsRemnant = Dimensions.Value < Product.Dimensions.Value [CheckBoxEditor(Editable = Editable.Hidden)] [ComplexFormula(typeof(IsRemnantCondition))] [EditorSequence(7)] public bool IsRemnant { get; set; } // Qty = Units * Dimensions.Value [EditorSequence(8)] [Formula(typeof(StockMovementQuantityFormula))] [DoubleEditor(Editable = Editable.Hidden, Summary = Summary.Sum)] public double Qty { get; set; } [CurrencyEditor(Visible = Visible.Default)] [EditorSequence(9)] public double Cost { get; set; } = 0.0; [EditorSequence(10)] [EntityRelationship(DeleteAction.SetNull)] public StockLocationLink Location { get; set; } [EditorSequence(11)] [EntityRelationship(DeleteAction.SetNull)] public JobLink Job { get; set; } [MemoEditor] [EditorSequence(12)] public string Notes { get; set; } [EditorSequence(13)] [EnumLookupEditor(typeof(StockMovementType), Visible = Visible.Default)] public StockMovementType Type { get; set; } [EditorSequence(14)] [EntityRelationship(DeleteAction.SetNull)] public EmployeeLink Employee { get; set; } [NullEditor] public Guid Transaction { get; set; } = Guid.NewGuid(); [NullEditor] public bool System { get; set; } [NullEditor] [Obsolete("Replaced with Type", true)] public bool IsTransfer { get; set; } = false; [NullEditor] public PurchaseOrderItemLink OrderItem { get; set; } private class JobRequisitionItemLookup : LookupDefinitionGenerator { public override Columns DefineColumns() { return Columns.None().Add(x => x.Job.JobNumber).Add(x => x.Requisition.Number).Add(x => x.Requisition.Description); } public override string FormatDisplay(CoreRow row) { var jobNumber = row.Get(x => x.Job.JobNumber); var requiNumber = row.Get(x => x.Requisition.Number); var requiDesc = row.Get(x => x.Requisition.Description); return $"{jobNumber}: #{requiNumber} ({requiDesc})"; } } [RequiredColumn] [LookupDefinition(typeof(JobRequisitionItemLookup))] public JobRequisitionItemLink JobRequisitionItem { get; set; } [NullEditor] public InvoiceLink Invoice { get; set; } private class JobScopeLookup : LookupDefinitionGenerator { public override Filter DefineFilter(StockMovement[] items) { var item = items?.Length == 1 ? items[0] : null; if (item != null) return new Filter(x => x.Job.ID).IsEqualTo(item.Job.ID).And(x => x.Status.Approved).IsEqualTo(true); return new Filter(x => x.ID).None(); } public override Columns DefineFilterColumns() => Columns.None().Add(x=>x.Job.ID); } [LookupDefinition(typeof(JobScopeLookup))] [EditorSequence(5)] [EntityRelationship(DeleteAction.SetNull)] public JobScopeLink JobScope { get; set; } public ActualCharge Charge { get; set; } [Aggregate(typeof(StockMovementDocumentCount))] [NullEditor] public int Documents { get; set; } /// /// Used to Group together movements (particularly images) /// when transactions are uploaded from Mobile Devices /// [EntityRelationship(DeleteAction.Cascade)] public StockMovementBatchLink Batch { get; set; } [NullEditor] [Obsolete("Replaced with Dimensions", true)] public double UnitSize { get; set; } /// /// Value of a stock movement, equal to * . /// [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden, Summary=Summary.Sum)] [Formula(typeof(StockMovementValueFormula))] public double Value { get; set; } = 0.0; [NullEditor] [LoggableProperty] public DateTime Posted { get; set; } [NullEditor] [LoggableProperty] [RequiredColumn] public PostedStatus PostedStatus { get; set; } [NullEditor] public string PostedNote { get; set; } [NullEditor] public string PostedReference { get; set; } static StockMovement() { StockEntity.LinkStockDimensions(); LinkedProperties.Register(x=>x.Product.DefaultInstance.Style, x => x.ID, x => x.Style.ID); //LinkedProperties.Register(x => x.Product, x => x.AverageCost, x => x.Cost); LinkedProperties.Register(ass => ass.Job.DefaultScope, scope => scope.ID, ass => ass.JobScope.ID); LinkedProperties.Register(ass => ass.Job.DefaultScope, scope => scope.Number, ass => ass.JobScope.Number); LinkedProperties.Register(ass => ass.Job.DefaultScope, scope => scope.Description, ass => ass.JobScope.Description); } private static Column unitsize = new Column(x => x.Dimensions.Value); private static Column issued = new Column(x => x.Issued); private static Column received = new Column(x => x.Received); private bool bChanging; protected override void DoPropertyChanged(string name, object? before, object? after) { if (bChanging) return; if (unitsize.IsEqualTo(name)) { bChanging = true; Qty = (Received - Issued) * (double)after; bChanging = false; } else if (issued.IsEqualTo(name)) { bChanging = true; Units = Received - (double)after; Qty = Units * Dimensions.Value; bChanging = false; } else if (received.IsEqualTo(name)) { bChanging = true; Units = ((double)after - Issued); Qty = Units * Dimensions.Value; bChanging = false; } base.DoPropertyChanged(name, before, after); } } }