Parcourir la source

Added ConsolidatedPurchaseOrderItem

Kenric Nugteren il y a 6 mois
Parent
commit
aeac90dc1c

+ 41 - 1
prs.classes/Entities/Job/Materials/JobMaterials.cs

@@ -88,6 +88,15 @@ namespace Comal.Classes
             AddTable<JobBillOfMaterialsItem>();
             AddTable<JobRequisitionItem>();
             AddTable<PurchaseOrderItem>();
+            var alloc = AddTable<PurchaseOrderItemAllocation>()
+                .AliasField(x => x.Product.ID, x => x.Item.Product.ID)
+                .AliasField(x => x.Style.ID, x => x.Item.Style.ID);
+            foreach(var column in Dimensions.LocalColumns<StockDimensions>())
+            {
+                alloc.AliasField(
+                    Column<IJobMaterial>.SubColumn(x => x.Dimensions, column),
+                    Column<PurchaseOrderItemAllocation>.SubColumn(x => x.Item.Dimensions, column));
+            }
         }
 
         public override bool Distinct => true;
@@ -170,13 +179,44 @@ namespace Comal.Classes
         private class OnOrderAggregate : ComplexFormulaGenerator<JobMaterial, double>
         {
             public override IComplexFormulaNode<JobMaterial, double> GetFormula() =>
+                Formula(FormulaOperator.Add,
+                    TotalPO(),
+                    If<JobMaterial, string, double>(
+                        Property<JobMaterial, string>(x => x.Dimensions.Unit.Conversion),
+                        Condition.Equals,
+                        Constant<JobMaterial, string>(""),
+                        "")
+                    .Then(TotalExplodedAllocations())
+                    .Else(Formula(FormulaOperator.Divide,
+                        TotalExplodedAllocations(),
+                        Property(x => x.Dimensions.Value))));
+
+            /// <summary>
+            /// Add up order item in "exploded" units.
+            /// </summary>
+            private IComplexFormulaNode<JobMaterial, double> TotalPO() =>
+                // All PO items
                 Aggregate(AggregateCalculation.Sum,
-                    x => x.Property(x => x.Qty),
+                    x => x.Property(x => x.Unallocated),
                     new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue))
                 .WithLink(x => x.Job.ID, x => x.Job.ID)
                 .WithLink(x => x.Product.ID, x => x.Product.ID)
                 .WithLink(x => x.Style.ID, x => x.Style.ID)
                 .WithLinks(Classes.Dimensions.GetLinks<PurchaseOrderItem, JobMaterial>(x => x.Dimensions, x => x.Dimensions));
+
+            /// <summary>
+            /// Add up order item in "exploded" units.
+            /// </summary>
+            private IComplexFormulaNode<JobMaterial, double> TotalExplodedAllocations() =>
+                // All allocations.
+                Aggregate(AggregateCalculation.Sum,
+                    x => x.Property(x => x.Quantity),
+                    new Filter<PurchaseOrderItemAllocation>(x => x.Item.ReceivedDate).IsEqualTo(DateTime.MinValue))
+                .WithLink(x => x.Job.ID, x => x.Job.ID)
+                .WithLink(x => x.Item.Product.ID, x => x.Product.ID)
+                .WithLink(x => x.Item.Style.ID, x => x.Style.ID)
+                .WithLinks(Classes.Dimensions.GetLinks<PurchaseOrderItemAllocation, JobMaterial>(x => x.Item.Dimensions, x => x.Dimensions));
+
         }
         [EditorSequence(9)]
         [DoubleEditor]

+ 10 - 1
prs.classes/Entities/PurchaseOrder/ConsolidatedPurchaseOrderItem.cs

@@ -65,7 +65,16 @@ namespace Comal.Classes
             AddTable<PurchaseOrderItemAllocation>()
                 .AliasField(x => x.Job.ID, x => x.Job.ID)
                 .AliasField(x => x.OrderItem.ID, x => x.Item.ID)
-                .AliasField(x => x.Qty, x => x.Quantity)
+                .AliasField(x => x.Qty,
+                    ComplexFormulaGenerator.If<PurchaseOrderItemAllocation, string, double>(
+                        ComplexFormulaGenerator.Property<PurchaseOrderItemAllocation, string>(x => x.Item.Dimensions.Unit.Conversion),
+                        Condition.Equals,
+                        ComplexFormulaGenerator.Constant<PurchaseOrderItemAllocation, string>(""),
+                        "")
+                    .Then(ComplexFormulaGenerator.Property<PurchaseOrderItemAllocation, double>(x => x.Quantity))
+                    .Else(ComplexFormulaGenerator.Formula(FormulaOperator.Divide,
+                        ComplexFormulaGenerator.Property<PurchaseOrderItemAllocation, double>(x => x.Quantity),
+                        ComplexFormulaGenerator.Property<PurchaseOrderItemAllocation, double>(x => x.Item.Dimensions.Value))))
                 .AliasField(x => x.JobRequisitionItem.ID, x => x.JobRequisitionItem.ID);
         }
 

+ 1 - 0
prs.desktop/Panels/Jobs/Orders/JobOrderGrid.cs

@@ -61,6 +61,7 @@ public class JobOrderGrid : DynamicDataGrid<ConsolidatedPurchaseOrderItem>, IMas
     	CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         criteria.Add(MasterDetailFilter);
+        criteria.Add(new Filter<ConsolidatedPurchaseOrderItem>(x => x.Qty).IsNotEqualTo(FilterConstant.Zero));
         base.Reload(criteria, columns, ref sort, token, action);
     }
 }

+ 81 - 26
prs.desktop/Panels/Jobs/Summary/JobSummaryGrid.cs

@@ -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);
                         }
 

+ 1 - 0
prs.shared/Database Update Scripts/DatabaseUpdateScripts.cs

@@ -58,5 +58,6 @@ public static class DatabaseUpdateScripts
         DataUpdater.RegisterUpdateScript<Update_8_24>();
         DataUpdater.RegisterUpdateScript<Update_8_25>();
         DataUpdater.RegisterUpdateScript<Update_8_32>();
+        DataUpdater.RegisterUpdateScript<Update_8_33>();
     }
 }

+ 16 - 0
prs.shared/Database Update Scripts/Update_8_33.cs

@@ -0,0 +1,16 @@
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared;
+
+internal class Update_8_33 : DatabaseUpdateScript
+{
+    public override VersionNumber Version => new(8, 33);
+
+    public override bool Update()
+    {
+        Logger.Send(LogType.Information, "", "Recreating views");
+        DbFactory.ProviderFactory.ForceRecreateViews();
+        return true;
+    }
+}