using System; using System.Linq; 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 { [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; 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", 2)] public ProductSupplierLink Supplier { get; set; } [CheckBoxEditor] [EditorSequence("Pricing", 3)] public bool UseDefaultSupplierPricing { get; set; } = true; [CurrencyEditor(Visible = Visible.Optional)] [EditorSequence("Pricing", 4)] public double BaseCost { get; set; } [Aggregate(typeof(ProductComponentCost))] [CurrencyEditor(Visible = Visible.Optional)] //, Editable = Editable.Disabled)] [EditorSequence("Pricing", 5)] public double ComponentCost { get; set; } [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Disabled)] [EditorSequence("Pricing", 6)] public double NettCost { get; set; } [Obsolete("Replaced with ProductInstance.AverageCost", true)] [CurrencyEditor(Visible = Visible.Optional)] [EditorSequence("Pricing", 7)] [LoggableProperty] public double AverageCost { get; set; } [EditorSequence("Pricing", 8)] [RequiredColumn] public TaxCodeLink TaxCode { get; set; } [EditorSequence("Pricing", 9)] [RequiredColumn] public PurchaseGLCodeLink PurchaseGL { get; set; } [EditorSequence("Pricing", 10)] [RequiredColumn] public SalesGLCodeLink SellGL { get; set; } [EditorSequence("Pricing", 11)] [RequiredColumn] public CostCentreLink CostCentre { get; set; } [EditorSequence("Pricing", 12)] public CostSheetSectionLink CostSheetSection { get; set; } [ProductChargeEditor] [EditorSequence("Pricing", 13)] 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)] public bool IsManufacturingTreatment { 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] public string Issues { 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; } } }