Browse Source

Added "Consolidate Holdings" button to Reservation management screen
Fixed BOM Calculations in Stock Forecast screen

frogsoftware 9 hours ago
parent
commit
dddadad201

+ 1 - 0
prs.classes/Entities/Stock/StockHolding/StockHolding.cs

@@ -180,6 +180,7 @@ namespace Comal.Classes
                     holding.Style.ID = first.Style.ID;
                     holding.Job.ID = first.Job.ID;
                     holding.Dimensions.CopyFrom(first.Dimensions);
+                    grouped.Add(holding);
                 }
                 holding.Recalculate(selected);
 

+ 136 - 7
prs.desktop/Panels/Reservation Management/ReservationManagementItemGrid.cs

@@ -16,6 +16,7 @@ using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
+using Microsoft.Xaml.Behaviors.Core;
 using Brush = System.Windows.Media.Brush;
 
 namespace PRSDesktop;
@@ -32,6 +33,7 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
     private Button ArchiveButton;
     private Button CreatePickingList;
     private Button CreateOrder;
+    private Button ConsolidateHoldings;
     
     public bool ShowColors { get; set; } 
     
@@ -191,8 +193,129 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
 
         ArchiveButton = AddButton("Archive", PRSDesktop.Resources.archive.AsBitmapImage(), ArchiveButton_Clicked);
         ArchiveButton.IsEnabled = false;
+        
+        ConsolidateHoldings = AddButton("Consolidate", PRSDesktop.Resources.product.AsBitmapImage(), ConsolidateHoldings_Clicked);
+        ConsolidateHoldings.IsEnabled = false;
     }
-    
+
+    private bool ConsolidateHoldings_Clicked(Button button, CoreRow[] rows)
+    {
+        ContextMenu menu = new ContextMenu();
+
+        menu.Items.Add( 
+            new MenuItem()
+            {
+                Header = "Move to Location",
+                Command = new ActionCommand(() =>
+                {
+                    MultiSelectDialog<StockLocation> dlg =
+                        new MultiSelectDialog<StockLocation>(new Filter<StockLocation>(x => x.Active).IsEqualTo(true),
+                            null, false);
+                    if (dlg.ShowDialog() == true)
+                        ConsolidateStockLocations(rows, dlg.Items().First());
+                })
+            }
+        );
+
+        menu.Items.Add(new Separator());
+        
+        menu.Items.Add( 
+            new MenuItem()
+            {
+                Header = "Create New Location",
+                Command = new ActionCommand(() =>
+                {
+                    var _location = new StockLocation();
+                    var _grid = DynamicGridUtils.CreateDynamicGrid<StockLocation>(typeof(DynamicDataGrid<>));
+                    if (_grid.EditItems( [ _location ] ))
+                    {
+                        ConsolidateStockLocations(rows, _location);
+                    }
+
+                })
+            }
+        );
+        
+        menu.IsOpen = true;
+
+        return false;
+
+    }
+
+    private void ConsolidateStockLocations(CoreRow[] rows, StockLocation location)
+    {
+        Progress.ShowModal("Consolidating Items", progress =>
+        {
+            var _ids = rows.Select(r => r.Get<JobRequisitionItem, Guid>(x => x.ID))
+                .Distinct()
+                .ToArray();
+            
+            var _allmovements = Client.Query(
+                new Filter<StockMovement>(x => x.JobRequisitionItem.ID).InList(_ids),
+                Columns.None<StockMovement>()
+                    .Add(x=>x.Product.ID)
+                    .Add(x=>x.Style.ID)
+                    .Add(x=>x.Location.ID)
+                    .Add(x=>x.Job.ID)
+                    .Add(x=>x.Units)
+                    .Add(x=>x.Cost)
+                    .Add(x=>x.JobRequisitionItem.ID)
+                    .AddDimensionsColumns(x=>x.Dimensions)
+            );
+            
+            if (_allmovements.Rows.Any())
+            {
+
+                progress.Report("Creating Batch");
+                var _batch = new StockMovementBatch();
+                _batch.TimeStamp = DateTime.Now;
+                _batch.Type = StockMovementBatchType.Transfer;
+                _batch.Employee.ID = App.EmployeeID;
+                Client.Save(_batch, "Consolidating requisition items into single location");
+                
+                progress.Report("Creating Transactions");
+                List<StockMovement> _updates = new();
+                foreach (var _id in _ids)
+                {
+                    var _movements = _allmovements.Rows
+                        .Where(r => r.Get<StockMovement,Guid>(x=>x.JobRequisitionItem.ID) == _id)
+                        .ToObjects<StockMovement>()
+                        .ToArray();
+
+                    var _holdings = StockHoldingExtensions.GroupMovements(_movements).ToArray();
+                    foreach (var _holding in _holdings)
+                    {
+                        var _transout = _holding.CreateMovement();
+                        _transout.Employee.ID = _batch.Employee.ID;
+                        _transout.Issued = _holding.Units;
+                        _transout.Cost = _holding.AverageValue;
+                        _transout.Type = StockMovementType.TransferOut;
+                        _transout.JobRequisitionItem.ID = _id;
+                        _transout.Batch.ID = _batch.ID;
+                        _transout.Notes = $"Consolidating requisition item holdings from {_holding.Location.Code} to {location.Code}";
+                        _updates.Add(_transout);
+
+                        var _transin = _holding.CreateMovement();
+                        _transin.Date = _transout.Date.AddTicks(1);
+                        _transin.Employee.ID = _batch.Employee.ID;
+                        _transin.Location.ID = location.ID;
+                        _transin.Received = _holding.Units;
+                        _transin.Cost = _holding.AverageValue;
+                        _transin.Type = StockMovementType.TransferIn;
+                        _transin.Transaction = _transout.Transaction;
+                        _transin.JobRequisitionItem.ID = _id;
+                        _transin.Batch.ID = _batch.ID;
+                        _transin.Notes = $"Consolidating requisition item holdings from {_holding.Location.Code} to {location.Code}";
+                        _updates.Add(_transin);
+                    }
+                    
+                }
+                Client.Save(_updates,$"Consolidated requisition item holdings to {location.Code}");
+            }
+        });
+
+    }
+
     #region CreatePurchaseOrder
     
     private bool DoCreatePurchaseOrder(Button button, CoreRow[]? rows)
@@ -423,12 +546,18 @@ public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
     protected override void SelectItems(CoreRow[]? rows)
     {
         base.SelectItems(rows);
-        if(rows?.Any() == true)
-        {
-            ArchiveButton.IsEnabled = true;
-            CreateOrder.IsEnabled = true;
-            CreatePickingList.IsEnabled = true;
-        }
+        
+        ArchiveButton.Visibility = Options.MultiSelect ? Visibility.Visible : Visibility.Collapsed;
+        CreateOrder.Visibility = Options.MultiSelect ? Visibility.Visible : Visibility.Collapsed;
+        CreatePickingList.Visibility = Options.MultiSelect ? Visibility.Visible : Visibility.Collapsed;
+        ConsolidateHoldings.Visibility = Options.MultiSelect ? Visibility.Visible : Visibility.Collapsed;
+
+        bool bAny = rows?.Any() == true;
+        ArchiveButton.IsEnabled = bAny;
+        CreateOrder.IsEnabled = bAny;
+        CreatePickingList.IsEnabled = bAny;
+        ConsolidateHoldings.IsEnabled = bAny;
+        
     }
 
     #region Action Column Buttons

+ 3 - 1
prs.desktop/Panels/Reservation Management/ReservationManagementPanel.xaml.cs

@@ -677,7 +677,9 @@ public partial class ReservationManagementPanel : UserControl, IPanel<JobRequisi
         _updatingSubstitution = true;
         try
         {
-            var row = e.Rows?.SingleOrDefault();
+            var row = Mode == PanelMode.Reserve && e.Rows?.Length == 1
+                ? e.Rows.FirstOrDefault()
+                : null;
             var visible =
                 row != null
                 && row.Get<JobRequisitionItem, double>(x => x.InStock).IsEffectivelyEqual(0.0)

+ 142 - 86
prs.desktop/Panels/Stock Forecast/StockForecastGrid.cs

@@ -15,6 +15,7 @@ using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
+using StockMovement = Comal.Classes.StockMovement;
 
 namespace PRSDesktop;
 
@@ -52,7 +53,7 @@ public class StockForecastItem : BaseObject
     public void AddJobBOM(Guid jobID, double quantity)
     {
         var item = JobInfo.GetValueOrAdd(jobID);
-        item.BOM += quantity;
+        item.BOM = Math.Max(0.0, item.BOM + quantity);
     }
     public void AddJobPO(Guid jobID, double quantity)
     {
@@ -388,8 +389,31 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
             return "";
         }
     }
-
-    private void ShowDetailGrid<TEntity>(
+    
+    private void ShowDetailGrid(String title, params Func<IDynamicDataGrid?>[] gridfuncs)
+    {
+        var _window = new ThemableWindow { Title = title };
+        var _tabcontrol = new DynamicTabControl() { TabStripPlacement = Dock.Bottom, Margin = new Thickness(5) };
+        _window.Content = _tabcontrol;
+        foreach (var gridfunc in gridfuncs)
+        {
+            var _grid = gridfunc();
+            if (_grid != null)
+            {
+                _tabcontrol.Items.Add(
+                    new DynamicTabItem()
+                    {
+                        Header = CoreUtils.Neatify(_grid.DataType.Name.Split('.').Last()),
+                        Content = _grid
+                    }
+                );
+                _grid.Refresh(true,true);
+            }
+        }
+        _window.ShowDialog();
+    }
+    
+    private IDynamicDataGrid BuildDetailGrid<TEntity>(
         String tag, 
         Expression<Func<TEntity,object?>> productcol, 
         Guid productid, 
@@ -402,41 +426,38 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
         Func<CoreRow,bool>? rowfilter
     )
     {
-        var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
-        if (grid == null)
+        var _grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
+        if (_grid == null)
         {
             MessageWindow.ShowError($"Cannot create Grid for [{typeof(TEntity).Name}]", "", shouldLog: false);
-            return;
+            return null;
         }
-        grid.ColumnsTag = $"{ColumnsTag}.{tag}";
-        grid.Reconfigure(options =>
+        _grid.ColumnsTag = $"{ColumnsTag}.{tag}";
+        _grid.Reconfigure(options =>
         {
             options.Clear();
             options.FilterRows = true;
             options.SelectColumns = true;
         });
-        grid.OnDefineFilter += t =>
+        _grid.OnDefineFilter += t =>
         {
-            var filter = new Filter<TEntity>(productcol).IsEqualTo(productid);
+            var _filter = new Filter<TEntity>(productcol).IsEqualTo(productid);
             if(dimensions is not null)
-            {
-                filter = filter.And(CoreUtils.GetFullPropertyName(dimcol, ".")).DimensionEquals(dimensions);
-            }
+                _filter = _filter.And(CoreUtils.GetFullPropertyName(dimcol, ".")).DimensionEquals(dimensions);
             
             if (styleid.HasValue)
-                filter = filter.And(stylecol).IsEqualTo(styleid);
+                _filter = _filter.And(stylecol).IsEqualTo(styleid);
 
             if (jobcol != null)
-                filter = filter.And(new Filter<TEntity>(jobcol).InList(JobIDs));
+                _filter = _filter.And(new Filter<TEntity>(jobcol).InList(JobIDs));
                 
             if (extrafilter != null)
-                filter = filter.And(extrafilter);
+                _filter = _filter.And(extrafilter);
             
-            return filter;
+            return _filter;
         };
-        grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
-        var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(tag)} Calculation", grid);
-        window.ShowDialog();
+        _grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
+        return _grid;
     }
 
     protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
@@ -451,70 +472,103 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
         switch (tag)
         {
             case ColumnTag.GeneralStockHoldings:
-                ShowDetailGrid<StockHolding>(
-                    ColumnTag.GeneralStockHoldings.ToString(), 
-                    x => x.Product.ID, 
-                    item.Product.ID, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x => x.Dimensions,
-                    item.Dimensions,
-                    null,
-                    new Filter<StockHolding>(x=>x.Job.ID).IsEqualTo(Guid.Empty),
-                    null);               
+                ShowDetailGrid(
+                    "Stock Holdings", 
+                    () => BuildDetailGrid<StockHolding>(
+                        ColumnTag.GeneralStockHoldings.ToString(), 
+                        x => x.Product.ID, 
+                     item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        null,
+                        new Filter<StockHolding>(x=>x.Job.ID).IsEqualTo(Guid.Empty),
+                        null
+                    )
+                );               
                 break;
             case ColumnTag.GeneralPurchaseOrders:
-                ShowDetailGrid<PurchaseOrderItem>(
-                    ColumnTag.GeneralPurchaseOrders.ToString(), 
-                    x => x.Product.ID, 
-                    item.Product.ID, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x => x.Dimensions,
-                    item.Dimensions,
-                    null,
-                    new Filter<PurchaseOrderItem>(x=>x.Job.ID).IsEqualTo(Guid.Empty)
-                        .And(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue),
-                    null);
+                ShowDetailGrid(
+                    "Purchase Orders", 
+                    () => BuildDetailGrid<PurchaseOrderItem>(
+                        ColumnTag.GeneralPurchaseOrders.ToString(), 
+                        x => x.Product.ID, 
+                        item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        null,
+                        new Filter<PurchaseOrderItem>(x=>x.Job.ID).IsEqualTo(Guid.Empty)
+                            .And(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue),
+                        null
+                    )
+                );
                 break;
             case ColumnTag.JobStockRequired:
-                ShowDetailGrid<JobBillOfMaterialsItem>(
-                    ColumnTag.JobStockRequired.ToString(), 
-                    x => x.Product.ID, 
-                    item.Product.ID, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x => x.Dimensions,
-                    item.Dimensions,
-                    x => x.Job.ID,
-                    new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
-                    null);
+                ShowDetailGrid(
+                    "Bills Of Materials", 
+                    () => BuildDetailGrid<JobBillOfMaterialsItem>(
+                        ColumnTag.JobStockRequired.ToString(), 
+                        x => x.Product.ID, 
+                        item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        x => x.Job.ID,
+                        new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
+                        null
+                    ),
+                    () => BuildDetailGrid<StockMovement>(
+                        "JobStockIssued", 
+                        x => x.Product.ID, 
+                        item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        x => x.Job.ID,
+                        new Filter<StockMovement>(x=>x.Type).IsEqualTo(StockMovementType.Issue),
+                        null
+                    )
+                    
+                );
                 break;
             case ColumnTag.JobStockHoldings:
-                ShowDetailGrid<StockHolding>(
-                    ColumnTag.JobStockHoldings.ToString(), 
-                    x => x.Product.ID, 
-                    item.Product.ID, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x => x.Dimensions,
-                    item.Dimensions,
-                    x => x.Job.ID,
-                    null,
-                    null);
+                ShowDetailGrid(
+                    "Stock Holdings", 
+                    () => BuildDetailGrid<StockHolding>(
+                        ColumnTag.JobStockHoldings.ToString(), 
+                        x => x.Product.ID, 
+                        item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        x => x.Job.ID,
+                        null,
+                        null
+                    )
+                );
                 break;
             case ColumnTag.JobPurchaseOrders:
-                ShowDetailGrid<PurchaseOrderItem>(
-                    ColumnTag.GeneralPurchaseOrders.ToString(), 
-                    x => x.Product.ID, 
-                    item.Product.ID, 
-                    x => x.Style.ID, 
-                    styleid, 
-                    x => x.Dimensions,
-                    item.Dimensions,
-                    x => x.Job.ID,
-                    new Filter<PurchaseOrderItem>(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue),
-                    null);                  
+                ShowDetailGrid(
+                    "Purchase Orders", 
+                    () => BuildDetailGrid<PurchaseOrderItem>(
+                        ColumnTag.GeneralPurchaseOrders.ToString(), 
+                        x => x.Product.ID, 
+                        item.Product.ID, 
+                        x => x.Style.ID, 
+                        styleid, 
+                        x => x.Dimensions,
+                        item.Dimensions,
+                        x => x.Job.ID,
+                        new Filter<PurchaseOrderItem>(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue),
+                        null
+                    )
+                );                  
                 break;
         }
     }
@@ -690,6 +744,7 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 filter: new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue),
                 columns: Columns.None<PurchaseOrderItem>().Add(x => x.Qty)),
             GetQuery<JobBillOfMaterialsItem>(
+                filter: new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
                 columns: Columns.None<JobBillOfMaterialsItem>().Add(x => x.Quantity)),
             GetQuery<StockMovement>(
                 filter: new Filter<StockMovement>(x => x.Type).IsEqualTo(StockMovementType.Issue),
@@ -769,20 +824,21 @@ public class StockForecastGrid : DynamicItemsListGrid<StockForecastItem>, IDataM
                 var jobBOMItems = results.Get<JobBillOfMaterialsItem>();
                 foreach(var bomItem in jobBOMItems.Rows)
                 {
-                    var item = GetItem(GetKey(bomItem));
+                    var key = GetKey(bomItem);
+                    var item = GetItem(key);
 
-                    item.JobBOM += bomItem.Get<JobBillOfMaterialsItem, double>(x => x.Quantity);
+                    item.JobBOM = Math.Max(0.0, item.JobBOM + bomItem.Get<JobBillOfMaterialsItem, double>(x => x.Quantity));
                     item.AddJobBOM(bomItem.Get<JobBillOfMaterialsItem, Guid>(x => x.Job.ID), bomItem.Get<JobBillOfMaterialsItem, double>(x => x.Quantity));
                 }
 
-                var movements = results.Get<StockMovement>();
-                foreach(var mvt in movements.Rows)
-                {
-                    var item = GetItem(GetKey(mvt));
-
-                    item.JobBOM -= mvt.Get<StockMovement, double>(x => x.Units);
-                    item.AddJobBOM(mvt.Get<StockMovement, Guid>(x => x.Job.ID), -mvt.Get<StockMovement, double>(x => x.Units));
-                }
+                // var movements = results.Get<StockMovement>();
+                // foreach(var mvt in movements.Rows)
+                // {
+                //     var item = GetItem(GetKey(mvt));
+                //
+                //     item.JobBOM = Math.Max(0.0, item.JobBOM + mvt.Get<StockMovement, double>(x => x.Units));
+                //     item.AddJobBOM(mvt.Get<StockMovement, Guid>(x => x.Job.ID), mvt.Get<StockMovement, double>(x => x.Units));
+                // }
 
                 _supplierProducts = results.GetArray<SupplierProduct>();