|
@@ -692,12 +692,27 @@ internal class JobSummaryGrid : DynamicDataGrid<JobMaterial>, IMasterDetailContr
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ private static Dictionary<Key, List<TSource>> GetDictionary<TSource>(IEnumerable<TSource> objs, bool hasJob, bool hasStyle, bool hasDimensions)
|
|
|
+ where TSource : IJobMaterial
|
|
|
+ {
|
|
|
+ var dict = new Dictionary<Key, List<TSource>>();
|
|
|
+ foreach(var obj in objs)
|
|
|
+ {
|
|
|
+ var key = new Key(
|
|
|
+ hasJob ? obj.Job.ID : Guid.Empty,
|
|
|
+ obj.Product.ID,
|
|
|
+ hasStyle ? obj.Style.ID : null,
|
|
|
+ hasDimensions ? obj.Dimensions : null);
|
|
|
+ dict.GetValueOrAdd(key).Add(obj);
|
|
|
+ }
|
|
|
+ return dict;
|
|
|
+ }
|
|
|
+
|
|
|
private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid? jobID, Key key, Func<CoreRow, bool>? extrafilter = null) where TSource : IJobMaterial
|
|
|
{
|
|
|
int jobcol = columns.IndexOf(x => x.Job.ID);
|
|
|
int productcol = columns.IndexOf(x => x.Product.ID);
|
|
|
int stylecol = key.StyleID.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
|
|
|
- int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
|
|
|
|
|
|
var dimCols = Dimensions.GetFilterColumnIndices<TSource>(columns, x => x.Dimensions);
|
|
|
|
|
@@ -742,50 +757,78 @@ internal class JobSummaryGrid : DynamicDataGrid<JobMaterial>, IMasterDetailContr
|
|
|
var table = new CoreTable();
|
|
|
table.LoadColumns(columns);
|
|
|
|
|
|
- var data = new Client<JobMaterial>().Query(criteria.Combine(), columns, orderby);
|
|
|
+ var data = Client.Query(criteria.Combine(), columns, orderby);
|
|
|
var pids = data.ExtractValues<JobMaterial, Guid>(x => x.Product.ID).ToArray();
|
|
|
|
|
|
- if (pids.Any())
|
|
|
+ var jobID = Master?.ID ?? Guid.Empty;
|
|
|
+
|
|
|
+ if (pids.Length != 0)
|
|
|
{
|
|
|
+ Filter<T> ReservesFilter<T>(Expression<Func<T, JobLink>> job)
|
|
|
+ {
|
|
|
+ return IncludeReserves
|
|
|
+ ? new Filter<T>().None()
|
|
|
+ : new Filter<T>(CoreUtils.GetFullPropertyName(job, ".") + "." + CoreUtils.GetFullPropertyName<JobLink, bool>(x => x.JobStatus.Active, ".")).IsEqualTo(false);
|
|
|
+ }
|
|
|
+
|
|
|
var results = Client.QueryMultiple(
|
|
|
new KeyedQueryDef<StockHolding>(
|
|
|
new Filter<StockHolding>(x => x.Product.ID).InList(pids)
|
|
|
.And(x => x.Units).IsNotEqualTo(0.0F)
|
|
|
- .And(new Filter<StockHolding>(x => x.Job.ID).IsEqualTo(Guid.Empty).Or(x => x.Job.ID).IsNotEqualTo(Master?.ID ?? Guid.Empty)),
|
|
|
+ .And(new Filter<StockHolding>(x => x.Job.ID).IsEqualTo(Guid.Empty).Or(x => x.Job.ID).IsNotEqualTo(jobID))
|
|
|
+ .And(ReservesFilter<StockHolding>(x => x.Job)),
|
|
|
Columns.None<StockHolding>().Add(x => x.Product.ID)
|
|
|
.Add(x => x.Style.ID)
|
|
|
.AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
|
|
|
.Add(x => x.Units)
|
|
|
.Add(x => x.Job.ID)
|
|
|
.Add(x => x.Job.JobStatus.Active)),
|
|
|
+
|
|
|
+ // PO Items for "Free Orders" calculation.
|
|
|
new KeyedQueryDef<PurchaseOrderItem>(
|
|
|
new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue)
|
|
|
.And(x => x.Product.ID).InList(pids)
|
|
|
- .And(new Filter<PurchaseOrderItem>(x => x.Job.ID).IsEqualTo(Guid.Empty).Or(x => x.Job.ID).IsNotEqualTo(Master?.ID ?? Guid.Empty)),
|
|
|
+ .And(new Filter<PurchaseOrderItem>(x => x.Job.ID).IsEqualTo(Guid.Empty)
|
|
|
+ .Or(x => x.Job.ID).IsNotEqualTo(jobID))
|
|
|
+ .And(ReservesFilter<PurchaseOrderItem>(x => x.Job)),
|
|
|
Columns.None<PurchaseOrderItem>().Add(x => x.Product.ID)
|
|
|
.Add(x => x.Style.ID)
|
|
|
.AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
|
|
|
- .Add(x => x.Qty)
|
|
|
+ .Add(x => x.Unallocated)
|
|
|
.Add(x => x.Job.ID)
|
|
|
- .Add(x => x.Job.JobStatus.Active)));
|
|
|
-
|
|
|
- var freestock = results.Get<StockHolding>();
|
|
|
- var freestockcolumns = Columns.None<StockHolding>().Add(freestock.Columns.Select(x => x.ColumnName));
|
|
|
-
|
|
|
- var freeorders = results.Get<PurchaseOrderItem>();
|
|
|
- var freeordercolumns = Columns.None<PurchaseOrderItem>().Add(freeorders.Columns.Select(x => x.ColumnName));
|
|
|
+ .Add(x => x.Job.JobStatus.Active)),
|
|
|
+ new KeyedQueryDef<PurchaseOrderItemAllocation>(
|
|
|
+ new Filter<PurchaseOrderItemAllocation>(x => x.Item.ReceivedDate).IsEqualTo(DateTime.MinValue)
|
|
|
+ .And(x => x.Item.Product.ID).InList(pids)
|
|
|
+ .And(new Filter<PurchaseOrderItemAllocation>(x => x.Job.ID).IsEqualTo(Guid.Empty)
|
|
|
+ .Or(x => x.Job.ID).IsNotEqualTo(jobID))
|
|
|
+ .And(ReservesFilter<PurchaseOrderItemAllocation>(x => x.Job)),
|
|
|
+ Columns.None<PurchaseOrderItemAllocation>()
|
|
|
+ .Add(x => x.Item.Product.ID)
|
|
|
+ .Add(x => x.Item.Style.ID)
|
|
|
+ .AddDimensionsColumns(x => x.Item.Dimensions, Dimensions.ColumnsType.Local)
|
|
|
+ .Add(x => x.Job.ID)
|
|
|
+ .Add(x => x.Job.JobStatus.Active)
|
|
|
+ .Add(x => x.Quantity)));
|
|
|
|
|
|
var hasStyle = StyleColumnVisible();
|
|
|
var hasDimensions = DimensionsColumnVisible();
|
|
|
var hasUnitSize = UnitSizeColumnVisible();
|
|
|
var keys = GetKeys(data.Rows, columns, hasStyle, hasDimensions);
|
|
|
|
|
|
+ var freeStock = GetDictionary(results.GetObjects<StockHolding>(), false, hasStyle, hasDimensions);
|
|
|
+ var freeOrders = GetDictionary(results.GetObjects<PurchaseOrderItem>(), false, hasStyle, hasDimensions);
|
|
|
+ var freeOrderAllocations = results.GetObjects<PurchaseOrderItemAllocation>()
|
|
|
+ .GroupBy(x => new Key(Guid.Empty, x.Item.Product.ID, hasStyle ? x.Item.Style.ID : null, hasDimensions ? x.Item.Dimensions : null))
|
|
|
+ .ToDictionary(x => x.Key, x => x.ToList());
|
|
|
+
|
|
|
foreach (var key in keys)
|
|
|
{
|
|
|
|
|
|
var rows = GetRows(data.Rows, columns, key.JobID, key);
|
|
|
if (rows.Length != 0)
|
|
|
{
|
|
|
+ // If we don't have the unit size column, then we need to multiply all units by the dimension size.
|
|
|
if (!hasUnitSize)
|
|
|
{
|
|
|
foreach(var row in rows)
|
|
@@ -797,6 +840,8 @@ internal class JobSummaryGrid : DynamicDataGrid<JobMaterial>, IMasterDetailContr
|
|
|
row.Update<JobMaterial, double>(x => x.Issued, x => x * multFactor);
|
|
|
row.Update<JobMaterial, double>(x => x.ReservedStock, x => x * multFactor);
|
|
|
row.Update<JobMaterial, double>(x => x.OnOrder, x => x * multFactor);
|
|
|
+
|
|
|
+ // None of these should actually have a value, since they're calculated fields; we overwrite them anyway.
|
|
|
row.Update<JobMaterial, double>(x => x.JobShortage, x => x * multFactor);
|
|
|
row.Update<JobMaterial, double>(x => x.FreeOnHand, x => x * multFactor);
|
|
|
row.Update<JobMaterial, double>(x => x.FreeOnOrder, x => x * multFactor);
|
|
@@ -805,7 +850,7 @@ internal class JobSummaryGrid : DynamicDataGrid<JobMaterial>, IMasterDetailContr
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- CoreRow newrow = table.NewRow();
|
|
|
+ var newrow = table.NewRow();
|
|
|
newrow.LoadValues(rows.First().Values);
|
|
|
|
|
|
var bom = Aggregate(rows, columns, hasStyle, true, x => x.BillOfMaterials, newrow,
|
|
@@ -822,20 +867,30 @@ internal class JobSummaryGrid : DynamicDataGrid<JobMaterial>, IMasterDetailContr
|
|
|
var shortage = Math.Max(0, Math.Max(0, (requi - issued)) - (reserved + ordered));
|
|
|
newrow.Set<JobMaterial, double>(x => x.JobShortage, shortage);
|
|
|
|
|
|
- var freestockrows = GetRows(freestock.Rows, freestockcolumns, null, key,
|
|
|
- IncludeReserves ? null : (r) => !r.Get<StockHolding, bool>(x => x.Job.JobStatus.Active));
|
|
|
- var freeonhand = Aggregate(freestockrows, freestockcolumns, hasStyle, false, x => x.Units, newrow,
|
|
|
- x => x.FreeOnHand);
|
|
|
- newrow.Set<JobMaterial, double>(x => x.FreeOnHand, freeonhand);
|
|
|
+ var indexKey = new Key(Guid.Empty, key.ProductID, key.StyleID, key.Dimensions);
|
|
|
+ var freeStockRows = freeStock.GetValueOrDefault(indexKey) ?? Enumerable.Empty<StockHolding>();
|
|
|
+ var freeOrderRows = freeOrders.GetValueOrDefault(indexKey) ?? Enumerable.Empty<PurchaseOrderItem>();
|
|
|
+ var freeOrderAllocationObjs = freeOrderAllocations.GetValueOrDefault(indexKey) ?? Enumerable.Empty<PurchaseOrderItemAllocation>();
|
|
|
+
|
|
|
+ double freeOnHand, freeOnOrder;
|
|
|
+ if (!hasUnitSize)
|
|
|
+ {
|
|
|
+ freeOnHand = freeStockRows.Sum(x => x.Units * x.Dimensions.Value);
|
|
|
+ freeOnOrder = freeOrderRows.Sum(x => x.Unallocated * x.Dimensions.Value)
|
|
|
+ + freeOrderAllocationObjs.Sum(x => x.Quantity);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ freeOnHand = freeStockRows.Sum(x => x.Units);
|
|
|
+ freeOnOrder = freeOrderRows.Sum(x => x.Unallocated)
|
|
|
+ + freeOrderAllocationObjs.Sum(x => x.Quantity * x.Item.Dimensions.Value);
|
|
|
+ }
|
|
|
|
|
|
- var freeorderrows = GetRows(freeorders.Rows, freeordercolumns, null, key,
|
|
|
- IncludeReserves ? null : (r) => !r.Get<PurchaseOrderItem, bool>(x => x.Job.JobStatus.Active));
|
|
|
- var freeonorder = Aggregate(freeorderrows, freeordercolumns, hasStyle, false, x => x.Qty, newrow,
|
|
|
- x => x.FreeOnOrder);
|
|
|
- newrow.Set<JobMaterial, double>(x => x.FreeOnOrder, freeonorder);
|
|
|
+ newrow.Set<JobMaterial, double>(x => x.FreeOnHand, freeOnHand);
|
|
|
+ newrow.Set<JobMaterial, double>(x => x.FreeOnOrder, freeOnOrder);
|
|
|
+ newrow.Set<JobMaterial, double>(x => x.FreeStockTotal, freeOnHand + freeOnOrder);
|
|
|
+ newrow.Set<JobMaterial, double>(x => x.FreeStockShortage, Math.Max(0, shortage - (freeOnHand + freeOnOrder)));
|
|
|
|
|
|
- newrow.Set<JobMaterial, double>(x => x.FreeStockTotal, freeonhand + freeonorder);
|
|
|
- newrow.Set<JobMaterial, double>(x => x.FreeStockShortage, Math.Max(0, shortage - (freeonhand + freeonorder)));
|
|
|
table.Rows.Add(newrow);
|
|
|
}
|
|
|
|