using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using InABox.Core; using PRSClasses; namespace Comal.Classes { public interface IProduct : IEntity { } [UserTracking("Product Management")] public class Product : Entity, IPersistent, IRemotable, IIssues, ILicense, IExportable, IImportable, IMergeable, IProblems { [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)] [EditorSequence(1)] public string Code { get; set; } [TextBoxEditor] [EditorSequence(2)] public string Name { get; set; } [MemoEditor] [EditorSequence(3)] public string Notes { get; set; } [EditorSequence(4)] public ProductDimensionUnitLink UnitOfMeasure { get; set; } private class ProductInstanceLookup : LookupDefinitionGenerator { public override Filter? DefineFilter(Product[] items) { if (items.Length == 0) { return new Filter().None(); } else { var filter = new Filters(); foreach (var item in items) { filter.Add(new Filter(x => x.Product.ID).IsEqualTo(item.ID)); } return filter.Combine(); } } public override Columns DefineFilterColumns() => Columns.None().Add(x => x.ID); } [LookupDefinition(typeof(ProductInstanceLookup))] [EditorSequence(5)] [RequiredColumn] public ProductInstanceLink DefaultInstance { get; set; } [EditorSequence(6)] public ProductGroupLink Group { get; set; } [EditorSequence(7)] [DataModelTableName("Image")] public ImageDocumentLink Image { get; set; } [URLEditor] [EditorSequence(8)] public string URL { get; set; } [TimestampEditor] [EditorSequence(9)] public DateTime Expired { get; set; } [EnumLookupEditor(typeof(ProductPricingStrategy))] [EditorSequence("Pricing", 1)] public ProductPricingStrategy PricingStrategy { get; set; } = ProductPricingStrategy.Standard; [EnumLookupEditor(typeof(SupplierProductOrderStrategy))] [EditorSequence("Pricing", 2)] public SupplierProductOrderStrategy OrderStrategy { get; set; } private class SupplierProductLookup : LookupDefinitionGenerator { public override Filter DefineFilter(Product[] items) { var id = items != null && items.Length == 1 ? items[0].ID : CoreUtils.FullGuid; return new Filter(x => x.Product.ID).IsEqualTo(id); } public override Columns DefineFilterColumns() => Columns.None().Add(x => x.ID); } [LookupDefinition(typeof(SupplierProductLookup))] [EditorSequence("Pricing", 3)] public ProductSupplierLink Supplier { get; set; } [CheckBoxEditor] [EditorSequence("Pricing", 4)] public bool UseDefaultSupplierPricing { get; set; } = true; [CurrencyEditor(Visible = Visible.Optional)] [EditorSequence("Pricing", 5)] public double BaseCost { get; set; } [Aggregate(typeof(ProductComponentCost))] [CurrencyEditor(Visible = Visible.Optional)] //, Editable = Editable.Disabled)] [EditorSequence("Pricing", 6)] public double ComponentCost { get; set; } [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Disabled)] [EditorSequence("Pricing", 7)] public double NettCost { get; set; } [Obsolete("Replaced with ProductInstance.AverageCost", true)] [CurrencyEditor(Visible = Visible.Optional)] [EditorSequence("Pricing", 8)] [LoggableProperty] public double AverageCost { get; set; } [EditorSequence("Pricing", 9)] [RequiredColumn] public TaxCodeLink TaxCode { get; set; } [EditorSequence("Pricing", 10)] [RequiredColumn] public PurchaseGLCodeLink PurchaseGL { get; set; } [EditorSequence("Pricing", 11)] [RequiredColumn] public SalesGLCodeLink SellGL { get; set; } [EditorSequence("Pricing", 12)] [RequiredColumn] public CostCentreLink CostCentre { get; set; } [EditorSequence("Pricing", 13)] public CostSheetSectionLink CostSheetSection { get; set; } [ProductChargeEditor] [EditorSequence("Pricing", 14)] public ProductCharge Charge { get; set; } /// /// Flag to indicate whether stock movements are to be tracked for this item. /// If set, no stock movements will be recorded, even if the Warehouse and/or default location are set. /// [CheckBoxEditor] [EditorSequence("Warehousing", 1)] public bool NonStock { get; set; } = false; /// /// The Warehouse that this item resides in. DefaultLocation Lookups are limited to this Warehouse only, although /// actual stock holdings can be anywhere (imperfect systems) /// [EditorSequence("Warehousing", 2)] public StockWarehouseLink Warehouse { get; set; } private class StockLocationLookup : LookupDefinitionGenerator { // If there are multiple items, they must be all in the same warehouse public override Filter DefineFilter(Product[] items) { var warehouses = items.Select(x => x.Warehouse.ID).Distinct().ToArray(); if (warehouses.Length == 1) return new Filter(x => x.Active).IsEqualTo(true).And(x => x.Warehouse.ID).IsEqualTo(warehouses.First()); return new Filter(x => x.ID).IsEqualTo(CoreUtils.FullGuid); } public override Columns DefineFilterColumns() => Columns.None().Add(x => x.Warehouse.ID); } [LookupDefinition(typeof(StockLocationLookup))] /// /// (Optional) The Location to be used when Purchase Orders / Consignments are received. /// If blank, the item will be received into the default location for the Warehouse assign to the product /// If that is also blank, then no stock movement will be recorded when receiving the item /// [EditorSequence("Warehousing", 4)] public StockLocationLink DefaultLocation { get; set; } private class ProductFormLookups : LookupDefinitionGenerator { public override Filter DefineFilter(Product[] items) { // Get all FillableFormTypes where 2nd parameter is typeof(PurchaseOrder) return new Filter(x => x.Active).IsEqualTo(true).And(x => x.AppliesTo).IsEqualTo("PurchaseOrderItem"); } public override Columns DefineFilterColumns() => Columns.None(); } [Caption("Incoming QA")] [EditorSequence("Warehousing", 5)] public DigitalFormLink DigitalForm { get; set; } [Caption("Mfg Treatment?")] //[EditorSequence("Warehousing", 6)] [NullEditor] [Obsolete("Replaced with TreatmentType", true)] public bool IsManufacturingTreatment { get; set; } private class TreatmentTypeLookup : LookupDefinitionGenerator { public override Filter? DefineFilter(Product[] items) { return new Filter(x => x.Active).IsEqualTo(true); } public override Columns DefineColumns() { return base.DefineColumns().Add(x => x.Description); } public override string FormatDisplay(CoreRow row) { return row.Get(x => x.Description); } } [LookupDefinition(typeof(TreatmentTypeLookup))] [EditorSequence("Warehousing", 6)] public TreatmentTypeLink TreatmentType { get; set; } [NullEditor] public string Barcodes { get; set; } [Aggregate(typeof(ProductUnitAggregate))] [DoubleEditor(Editable = Editable.Hidden)] public double UnitQty { get; set; } [Aggregate(typeof(ProductRemnantAggregate))] [DoubleEditor(Editable = Editable.Hidden)] public double RemnantQty { get; set; } [Aggregate(typeof(OnOrderAggregate))] [DoubleEditor(Editable = Editable.Hidden)] public double OnOrder { get; set; } [Aggregate(typeof(TotalStockAggregate))] [DoubleEditor(Editable = Editable.Hidden)] public double TotalStock { get; set; } [Formula(typeof(ReservedStockFormula))] [DoubleEditor(Editable = Editable.Hidden)] public double ReservedStock { get; set; } [Aggregate(typeof(FreeStockAggregate))] [DoubleEditor(Editable = Editable.Hidden)] public double FreeStock { get; set; } [Aggregate(typeof(TotalRequiredAggregate))] [DoubleEditor(Editable = Editable.Hidden)] public double Required { get; set; } [Aggregate(typeof(ProductReceivedAggregate))] [DateTimeEditor(Editable = Editable.Hidden)] public DateTime LastReceived { get; set; } [Aggregate(typeof(ProductIssuedAggregate))] [DateTimeEditor(Editable = Editable.Hidden)] public DateTime LastIssued { get; set; } [Obsolete("Replaced with DefaultInstance.Style")] [NullEditor] public ProductStyleLink DefaultStyle { get; set; } [Obsolete("Replaced with DefaultInstance.MinimumStockLevel")] [NullEditor] public int MinimumStockLevel { get; set; } [Obsolete("Replaced with DefaultInstance.Dimensions")] [NullEditor] public ProductDimensions Dimensions { get; set; } [NullEditor] [Obsolete("Replaced with Dimensions", true)] // [DoubleEditor] // [EditorSequence(4)] public double UnitSize { get; set; } [NullEditor] [Obsolete("Replaced with Dimensions", false)] // [EditorSequence(5)] public ProductUOMLink Units { get; set; } [NullEditor] [Obsolete("Replaced with Dimensions", true)] // [DoubleEditor] // [EditorSequence(6)] // [Caption("Standard Weight")] public double Weight { get; set; } [NullEditor] [Obsolete("Replaced with Problem", true)] public string Issues { get; set; } [EditorSequence("Issues", 1)] public ManagedProblem Problem { get; set; } public override string ToString() { return string.Format("{0}: {1} ({2})", Code, Name, DefaultInstance.Dimensions.UnitSize); } public static double CalculateNettCost(ProductPricingStrategy strategy, double basecost, double componentcost) { if (strategy == ProductPricingStrategy.Kit) return basecost + componentcost; if (strategy == ProductPricingStrategy.Subcontractor) return basecost - componentcost; // Standard or Assembly return basecost; } } public class FreeStockAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Qty; public override Filter Filter => new Filter(x => x.Job).NotLinkValid(); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class OnOrderAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Qty; //public Expression> Link => x => x.ProductLink.ID; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { PurchaseOrderItem => PurchaseOrderItem.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class ProductReceivedAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Date; public override Filter Filter => new Filter(x => x.Type).IsEqualTo(StockMovementType.Receive); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Maximum; } public class ProductRemnantAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Units; public override Filter Filter => new Filter(x => x.IsRemnant).IsEqualTo(true); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class ProductUnitAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Units; public override Filter Filter => new Filter(x => x.IsRemnant).IsEqualTo(false); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class ReservedStockFormula : IFormula { public Expression> Value => x => x.TotalStock; public Expression>[] Modifiers => new Expression>[] { x => x.FreeStock }; public FormulaOperator Operator => FormulaOperator.Subtract; public FormulaType Type => FormulaType.Virtual; } public class StockShortageFormula : IFormula { public Expression> Value => x => x.TotalStock; public Expression>[] Modifiers => new Expression>[] { x => x.Required }; public FormulaOperator Operator => FormulaOperator.Subtract; public FormulaType Type => FormulaType.Virtual; } public class TotalRequiredAggregate : CoreAggregate { public override Expression> Aggregate => x => x.TotalQty; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { JobMaterialRequisitionItem => JobMaterialRequisitionItem.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class TotalStockAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Qty; public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Sum; } public class ProductIssuedAggregate : CoreAggregate { public override Expression> Aggregate => x => x.Date; public override Filter Filter => new Filter(x => x.Type).IsEqualTo(StockMovementType.Issue); public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>() { { StockMovement => StockMovement.Product.ID, Product => Product.ID } }; public override AggregateCalculation Calculation => AggregateCalculation.Maximum; } }