using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using InABox.Clients;
using InABox.Core;
using PRSClasses;
namespace Comal.Classes
{
public enum JobRequisitionItemStatus
{
NotChecked, // Default
///
/// All required stock has been received, and is in the correct .
///
///
/// This can be set even if there are unreceived ,
/// since if we got the stock some other way, we still think of it as allocated.
///
Allocated,
///
/// All required stock has been received, but some is not in the correct , meaning a treatment is required.
///
///
/// This can be set even if there are unreceived ,
/// since if we got the stock some other way, we still think of it as having the stock allocated.
///
TreatmentRequired,
///
/// The has been set, but there are no s for
/// this .
///
OrderRequired,
///
/// We don't yet have all the stock, and there is at least one unreceived of type
/// .
///
OnOrder,
///
/// We don't yet have all the stock, and there is at least one unreceived of type
/// and none of type .
///
TreatmentOnOrder,
[Obsolete]
Received,// Drop
[Obsolete]
TreatmentReceived,// Drop
///
/// The has been cancelled, meaning it has a non-empty .
///
Cancelled,
///
/// The has been archived, meaning it has a non-empty .
///
Archived,
///
/// The has been issued, meaning that it has been allocated, and there are stock movements of type adding up to the correct total.
///
Issued
}
public class JobRequisitionItemTotalQtyFormula : IFormula
{
public Expression> Value => x => x.Qty;
public Expression>[] Modifiers => new Expression>[] { x => x.Dimensions.Value };
public FormulaOperator Operator => FormulaOperator.Multiply;
public FormulaType Type => FormulaType.Virtual;
}
public class JobRequisitionItemInStockAggregate : CoreAggregate
{
public override Expression> Aggregate => x => x.Units;
public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>
{
{ x => x.JobRequisitionItem.ID, x => x.ID }
};
public override AggregateCalculation Calculation => AggregateCalculation.Sum;
}
public class JobRequisitionItemOrdersAggregate : CoreAggregate
{
public override Expression> Aggregate => x => x.PurchaseOrderItem.Qty;
public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>
{
{ x => x.JobRequisitionItem.ID, x => x.ID },
};
public override AggregateCalculation Calculation => AggregateCalculation.Sum;
public override Filter? Filter =>
new Filter(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(null);
}
public class JobRequisitionItemOnOrderAggregate : CoreAggregate
{
public override Expression> Aggregate => x => x.PurchaseOrderItem.Qty;
public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>
{
{ x => x.JobRequisitionItem.ID, x => x.ID },
{ x => x.PurchaseOrderItem.Product.ID, x => x.Product.ID },
};
public override AggregateCalculation Calculation => AggregateCalculation.Sum;
public override Filter? Filter =>
new Filter(x => x.PurchaseOrderItem.ReceivedDate).IsEqualTo(null);
}
public class JobRequisitionItemAllocatedAggregate : CoreAggregate
{
public override Expression> Aggregate => x => x.Units;
public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>
{
{ x => x.JobRequisitionItem.ID, x => x.ID },
{ x => x.Style.ID, x => x.Style.ID },
};
public override AggregateCalculation Calculation => AggregateCalculation.Sum;
}
public class JobRequisitionItemTreatmentOnOrderFormula : IFormula
{
public Expression> Value => x => x.TotalOrders;
public Expression>[] Modifiers => new Expression>[] { x => x.OnOrder };
public FormulaOperator Operator => FormulaOperator.Subtract;
public FormulaType Type => FormulaType.Virtual;
}
public class JobRequisitionItemTreatmentRequiredFormula : IFormula
{
public Expression> Value => x => x.InStock;
public Expression>[] Modifiers => new Expression>[] { x => x.Allocated };
public FormulaOperator Operator => FormulaOperator.Subtract;
public FormulaType Type => FormulaType.Virtual;
}
public class JobRequisitionItemIssuedAggregate : CoreAggregate
{
public override Expression> Aggregate => x => x.Issued;
public override Dictionary>, Expression>> Links => new Dictionary>, Expression>>
{
{ x => x.JobRequisitionItem.ID, x => x.ID },
{ x => x.Style.ID, x => x.Style.ID },
};
public override Filter? Filter => new Filter(x => x.Type).IsEqualTo(StockMovementType.Issue);
public override AggregateCalculation Calculation => AggregateCalculation.Sum;
}
public interface IJobRequisitionItem : IEntity
{
}
[Caption("Items")]
[UserTracking(typeof(Job))]
public class JobRequisitionItem : StockEntity, IRemotable, IPersistent, IOneToMany,
ILicense, IJobMaterial, ISequenceable, IIssues, IJobRequisitionItem
{
[EntityRelationship(DeleteAction.Cascade)]
[Editable(Editable.Hidden)]
public JobLink Job { get; set; }
[EntityRelationship(DeleteAction.Cascade)]
[Editable(Editable.Hidden)]
public JobRequisitionLink Requisition { get; set; }
[EntityRelationship(DeleteAction.SetNull)]
[EditorSequence(1)]
[RequiredColumn]
public override ProductLink Product { get; set; }
[EditorSequence(2)]
[RequiredColumn]
public ProductStyleLink Style { get; set; }
[NullEditor]
[Obsolete("Replaced with Dimensions", true)]
public double UnitSize { get; set; }
[EditorSequence(3)]
[RequiredColumn]
[DimensionsEditor(typeof(StockDimensions))]
public override StockDimensions Dimensions { get; set; }
[EditorSequence(4)]
[RequiredColumn]
public double Qty { get; set; }
[DoubleEditor(Editable = Editable.Hidden)]
[Formula(typeof(JobRequisitionItemTotalQtyFormula))]
public double TotalQty { get; set; }
[EditorSequence(5)]
public double UnitCost { get; set; }
[EditorSequence(6)]
[CurrencyEditor(Summary = Summary.Sum)]
public double TotalCost { get; set; }
[EditorSequence(7)]
[MemoEditor]
public string Notes { get; set; }
[EditorSequence(8)]
public SupplierLink Supplier { get; set; }
[EnumLookupEditor(typeof(JobRequisitionItemStatus), Editable = Editable.Disabled)]
[EditorSequence(9)]
[LoggableProperty]
[RequiredColumn]
public JobRequisitionItemStatus Status { get; set; } = JobRequisitionItemStatus.NotChecked;
///
/// The amount of this requisition item that is currently in stock, which is an aggregate of the property.
///
[Aggregate(typeof(JobRequisitionItemInStockAggregate))]
[DoubleEditor(Editable = Editable.Disabled)]
[EditorSequence(10)]
public double InStock { get; set; }
[Aggregate(typeof(JobRequisitionItemOrdersAggregate))]
[DoubleEditor(Editable = Editable.Disabled, Visible = Visible.Optional)]
[EditorSequence(11)]
public double TotalOrders { get; set; }
[Aggregate(typeof(JobRequisitionItemOnOrderAggregate))]
[DoubleEditor(Editable = Editable.Disabled)]
[EditorSequence(12)]
public double OnOrder { get; set; }
[Formula(typeof(JobRequisitionItemTreatmentRequiredFormula))]
[DoubleEditor(Editable = Editable.Disabled)]
[EditorSequence(13)]
public double TreatmentRequired { get; set; }
[Formula(typeof(JobRequisitionItemTreatmentOnOrderFormula))]
[DoubleEditor(Editable = Editable.Disabled)]
[EditorSequence(14)]
public double TreatmentOnOrder { get; set; }
[Aggregate(typeof(JobRequisitionItemAllocatedAggregate))]
[DoubleEditor(Editable = Editable.Disabled)]
[EditorSequence(15)]
public double Allocated { get; set; }
[Aggregate(typeof(JobRequisitionItemIssuedAggregate))]
[DoubleEditor(Editable = Editable.Disabled)]
[EditorSequence(16)]
public double Issued { get; set; }
[EntityRelationship(DeleteAction.SetNull)]
[RequiredColumn]
[Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")]
[NullEditor]
public PurchaseOrderItemLink PurchaseOrderItem { get; set; }
[Aggregate(typeof(JobRequisitionItemPurchaseOrderNumberAggregate))]
[TextBoxEditor(Editable = Editable.Hidden)]
public string PurchaseOrderNumbers { get; set; }
[RequiredColumn]
[EditorSequence(17)]
public DateTime Cancelled { get; set; } = DateTime.MinValue;
[RequiredColumn]
[EditorSequence(18)]
public DateTime Archived { get; set; } = DateTime.MinValue;
[RequiredColumn]
[Obsolete("Replaced with JobRequisitionItemPurchaseOrderItem")]
[NullEditor]
public DateTime Ordered { get; set; } = DateTime.MinValue;
[RequiredColumn]
[EditorSequence(19)]
public DateTime OrderRequired { get; set; } = DateTime.MinValue;
[NullEditor]
public string Issues { get; set; }
[NullEditor]
public long Sequence { get; set; }
static JobRequisitionItem()
{
LinkedProperties.Register(x => x.Product.DefaultInstance.Style, x => x.ID, x => x.Style.ID);
LinkedProperties.Register(x => x.Product.DefaultInstance.Style, x => x.Code, x => x.Style.Code);
LinkedProperties.Register(x => x.Product.DefaultInstance.Style, x => x.Description, x => x.Style.Description);
LinkedProperties.Register(x => x.Product.DefaultInstance, x => x.NettCost,
x => x.UnitCost);
StockEntity.LinkStockDimensions();
}
private bool bChanging;
protected override void DoPropertyChanged(string name, object? before, object? after)
{
if (bChanging)
return;
try
{
bChanging = true;
if (name.Equals(nameof(Qty)) && after is double qty)
TotalCost = UnitCost * qty;
else if (name.Equals(nameof(UnitCost)) && after is double cost)
TotalCost = cost * Qty;
else if (name.Equals(nameof(TotalCost)) && after is double total)
{
if (Qty == 0)
Qty = 1;
UnitCost = total / Qty;
}
}
finally
{
bChanging = false;
}
base.DoPropertyChanged(name, before, after);
}
public static void UpdateCosts(IEnumerable items, Dictionary changes)
{
void UpdateValue(JobRequisitionItem item,Expression> property, TType value)
{
CoreUtils.MonitorChanges(
item,
() => CoreUtils.SetPropertyValue(item, CoreUtils.GetFullPropertyName(property, "."), value),
changes
);
}
var productids = items.Where(x => x.Product.ID != Guid.Empty).Select(x => x.Product.ID).ToArray();
MultiQuery query = new MultiQuery();
query.Add(
new Filter(x=>x.Product.ID).InList(productids),
Columns.None().Add(x=>x.Product.ID)
.Add(x=>x.SupplierLink.ID)
.Add(x=>x.Style.ID)
.AddDimensionsColumns(x => x.Dimensions, Classes.Dimensions.ColumnsType.Local)
.Add(x=>x.Job.ID)
.Add(x=>x.SupplierCode)
.Add(x=>x.SupplierDescription)
.Add(x=>x.CostPrice)
);
query.Add(
new Filter(x=>x.Product.ID).InList(productids),
Columns.None().Add(x=>x.Product.ID)
.Add(x => x.Style.ID)
.AddDimensionsColumns(x => x.Dimensions, Classes.Dimensions.ColumnsType.Local)
.Add(x => x.NettCost)
);
query.Query();
var supplierProducts = query.Get().ToArray();
var productInstances = query.Get().ToArray();
foreach (var item in items)
{
//Check Supplier / Job Specific Pricing
var supplierProduct = supplierProducts.FirstOrDefault(x =>
x.Product.ID == item.Product.ID
&& x.SupplierLink.ID == item.Supplier.ID
&& x.Style.ID == item.Style.ID
&& x.Dimensions.Equals(item.Dimensions)
&& x.Job.ID == item.Job.ID);
if (supplierProduct != null)
{
UpdateValue(item, x => x.UnitCost, supplierProduct.CostPrice);
continue;
}
// Check Supplier Pricing
supplierProduct = supplierProducts.FirstOrDefault(x =>
x.Product.ID == item.Product.ID
&& x.SupplierLink.ID == item.Supplier.ID
&& x.Style.ID == item.Style.ID
&& x.Dimensions.Equals(item.Dimensions)
&& x.Job.ID == Guid.Empty);
if (supplierProduct != null)
{
UpdateValue(item, x => x.UnitCost, supplierProduct.CostPrice);
continue;
}
// Check Specific Product Instance
var productInstance = productInstances.FirstOrDefault(x =>
x.Product.ID == item.Product.ID
&& x.Style.ID == item.Style.ID
&& x.Dimensions.Equals(item.Dimensions));
if (productInstance != null)
UpdateValue(item, x => x.UnitCost, productInstance.NettCost);
else
UpdateValue(item, x => x.UnitCost, 0.0F);
}
}
}
public class JobRequisitionItemPurchaseOrderNumberAggregate : CoreAggregate
{
public override Expression> Aggregate => x => x.PurchaseOrderItem.PurchaseOrderLink.PONumber;
public override Dictionary>, Expression>> Links { get; } = new Dictionary>, Expression>>
{
{ x => x.JobRequisitionItem.ID, x => x.ID }
};
public override AggregateCalculation Calculation => AggregateCalculation.Concat;
}
}