Browse Source

Added BOM and JRI Editors to Requisition Item Editor Screen

frogsoftware 1 year ago
parent
commit
51b5431a79

+ 3 - 3
prs.classes/Entities/Job/Requisitions/JobRequisitionItemLink.cs

@@ -30,11 +30,11 @@ namespace Comal.Classes
 
         [EditorSequence(3)]
         public double Qty { get; set; }
-
-        [DoubleEditor]
+        
         [EditorSequence(4)]
-        public double UnitSize { get; set; }
+        public StockDimensions Dimensions { get; set; }
 
+        [EditorSequence(5)]
         [EnumLookupEditor(typeof(JobRequisitionItemStatus))]
         public JobRequisitionItemStatus Status { get; set; } = JobRequisitionItemStatus.NotChecked;
 

+ 19 - 2
prs.desktop/Panels/Jobs/Picking Lists/JobPickingListItemGrid.cs

@@ -20,7 +20,7 @@ internal class JobPickingListItemGrid : DynamicDataGrid<RequisitionItem>, ISpeci
     public Requisition? Master { get; set; }
 
     private Task<List<Product>>? _products;
-
+    
     public Filter<RequisitionItem> MasterDetailFilter => Master != null
         ? new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(Master.ID)
         : new Filter<RequisitionItem>().None();
@@ -29,9 +29,26 @@ internal class JobPickingListItemGrid : DynamicDataGrid<RequisitionItem>, ISpeci
     {
         base.Init();
 
+        HiddenColumns.Add(x => x.RequisitionLink.JobLink.ID);
+        HiddenColumns.Add(x => x.Product.ID);
+        HiddenColumns.Add(x => x.Style.ID);
+        HiddenColumns.Add(x => x.JobLink.ID);
+        HiddenColumns.Add(x =>x.Dimensions.Unit.ID);
+        HiddenColumns.Add(x => x.Dimensions.UnitSize);
+        HiddenColumns.Add(x => x.JobRequisitionItem.ID);
+        HiddenColumns.Add(x => x.Location.ID);
         HiddenColumns.Add(x => x.EditType);
         HiddenColumns.Add(x => x.Done);
     }
+    
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        options.AddRange(
+            DynamicGridOption.SelectColumns,
+            DynamicGridOption.FilterRows
+        );
+    }
 
     protected override void Reload(Filters<RequisitionItem> criteria, Columns<RequisitionItem> columns,
         ref SortOrder<RequisitionItem>? sort,
@@ -60,7 +77,7 @@ internal class JobPickingListItemGrid : DynamicDataGrid<RequisitionItem>, ISpeci
             item = CreateItem();
         }
 
-        var editor = new RequisitionItemEditor(GetProducts(), item, false);
+        var editor = new RequisitionItemEditor(GetProducts, item, false);
         if(editor.ShowDialog() == true && editor.Result is not null)
         {
             Refresh(false, true);

+ 220 - 0
prs.desktop/Panels/Requisitions/RequisitionItemEditor/RequisitionItemBillOfMaterialsPage.cs

@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Media;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace PRSDesktop.Panels.Requisitions;
+
+public class JobBOMSummary : BaseObject
+{
+    
+    public ProductLink Product { get; set; }
+    
+    public ProductStyleLink Style { get; set; }
+    
+    public StockDimensions Dimensions { get; set; }
+    
+        
+    [DoubleEditor(Editable = Editable.Disabled)]
+    public double BOM { get; set; }
+    
+    [DoubleEditor(Editable = Editable.Disabled)]
+    public double Issued { get; set; }
+    
+    [DoubleEditor]
+    public double Quantity { get; set; }
+}
+
+public class RequisitionItemBillOfMaterialsPage : DynamicItemsListGrid<JobBOMSummary>, ISpecificGrid
+{
+    
+    public RequisitionItem Item { get; set; }
+    
+    private class RequisitionItemBillOfMaterialsPageUI : DynamicGridGridUIComponent<JobBOMSummary>
+    {
+        protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
+        {
+            if (!row.Get<JobBOMSummary, double>(c => c.Quantity).IsEffectivelyEqual(0.0))
+                return new SolidColorBrush(Colors.LightGreen);
+            return base.GetCellBackground(row, column);
+        }
+    }
+
+    protected override IDynamicGridUIComponent<JobBOMSummary> CreateUIComponent() => new RequisitionItemBillOfMaterialsPageUI() { Parent = this };
+
+    protected override void Init()
+    {
+        base.Init();
+        HiddenColumns.Add(x=>x.Product.ID);
+        HiddenColumns.Add(x => x.Product.Group.ID);
+        HiddenColumns.Add(x=>x.Style.ID);
+        HiddenColumns.Add(x=>x.Dimensions.Unit.ID);
+        HiddenColumns.Add(x=>x.Dimensions.UnitSize);
+        HiddenColumns.Add(x=>x.Quantity);
+
+    }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        options.BeginUpdate()
+            .Clear()
+            .Add(DynamicGridOption.FilterRows)
+            .Add(DynamicGridOption.DirectEdit)
+            .Add(DynamicGridOption.HideDirectEditButton)
+            .EndUpdate();
+    }
+
+    protected override void Reload(Filters<JobBOMSummary> criteria, Columns<JobBOMSummary> columns, ref SortOrder<JobBOMSummary>? sort, Action<CoreTable?, Exception?> action)
+    {
+        Items.Clear();
+        var table = new CoreTable();
+        table.LoadColumns(columns);
+
+        Filters<JobBillOfMaterialsItem> itemfilters = new Filters<JobBillOfMaterialsItem>();
+        itemfilters.Add(
+            new Filter<JobBillOfMaterialsItem>(x => x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID));
+        foreach (var filter in criteria.Items)
+        {
+            var itemfilter = Serialization.Deserialize<Filter<JobBillOfMaterialsItem>>(Serialization.Serialize(filter));
+            if (itemfilter is not null)
+                itemfilters.Add(itemfilter);
+        }
+
+        Columns<JobBillOfMaterialsItem> itemcolumns = new Columns<JobBillOfMaterialsItem>();
+        foreach (var column in columns)
+            itemcolumns.Add(column.Property);
+        
+        SortOrder<JobBillOfMaterialsItem>? itemsort =
+            Serialization.Deserialize<SortOrder<JobBillOfMaterialsItem>>(Serialization.Serialize(sort));
+
+        MultiQuery query = new();
+        query.Add<JobBillOfMaterialsItem>(
+            itemfilters.Combine(), 
+            itemcolumns, 
+            itemsort
+        );
+        query.Add<StockMovement>(
+            new Filter<StockMovement>(x=>x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID)
+                .And(x=>x.Type).IsEqualTo(StockMovementType.Issue),
+            new Columns<StockMovement>(x=>x.Product.ID)
+                .Add(x=>x.Style.ID)
+                .Add(x=>x.Dimensions.Unit.ID)
+                .Add(x=>x.Dimensions.UnitSize)
+                .Add(x=>x.Issued)
+        );    
+        
+        query.Query(q =>
+        {
+            try
+            {
+                var mvmts = q.Get<StockMovement>()?.Rows.Select(r => r.ToObject<StockMovement>()).ToArray();
+                var bomItems = q.Get<JobBillOfMaterialsItem>()?.Rows.Select(r => r.ToObject<JobBillOfMaterialsItem>()).ToArray();
+
+                if (mvmts is not null && bomItems is not null)
+                {
+
+                    var bomGroups = bomItems?.GroupBy<JobBillOfMaterialsItem, Tuple<Guid, Guid, Guid, String>>(x =>
+                        new Tuple<Guid, Guid, Guid, String>(x.Product.ID, x.Style.ID, x.Dimensions.Unit.ID,
+                            x.Dimensions.UnitSize));
+
+                    foreach (var group in bomGroups)
+                    {
+
+                        JobBOMSummary summary = new JobBOMSummary();
+                        summary.Product.ID = group.Key.Item1;
+                        summary.Product.Synchronise(group.First().Product);
+                        summary.Style.ID = group.Key.Item2;
+                        summary.Style.Synchronise(group.First().Style);
+                        summary.Dimensions.CopyFrom(group.First().Dimensions);
+                    
+                        var issues = mvmts.Where(x =>
+                            x.Product.ID == group.Key.Item1
+                            && x.Style.ID == group.Key.Item2
+                            && x.Dimensions.Unit.ID == group.Key.Item3
+                            && Equals(x.Dimensions.UnitSize, group.Key.Item4)
+                        );
+
+                        summary.BOM = group.Sum(x => x.Quantity);
+                        summary.Issued = issues.Sum(x=>x.Issued);
+
+                        if (summary.Product.ID == Item.Product.ID
+                            && summary.Style.ID == Item.Style.ID
+                            && summary.Dimensions.Unit.ID == Item.Dimensions.Unit.ID
+                            && summary.Dimensions.UnitSize.Equals(Item.Dimensions.UnitSize)
+                        )
+                            summary.Quantity = Item.Quantity;
+                        
+                    
+                        Items.Add(summary);
+                    }
+                
+                    table.LoadRows(Items);
+                }
+
+                action(table, null);
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e);
+
+            }
+            
+        });
+        
+    }
+
+    private bool bFirst = true;
+
+    protected override void OnAfterRefresh()
+    {
+        base.OnAfterRefresh();
+        if (!bFirst)
+            return;
+        bFirst = false;
+        
+        SelectedRows = Data.Rows.Where(r => 
+            r.Get<JobBOMSummary, Guid>(c => c.Product.ID) == Item.Product.ID 
+            && r.Get<JobBOMSummary, Guid>(c => c.Style.ID) == Item.Style.ID
+            && r.Get<JobBOMSummary, Guid>(c => c.Dimensions.Unit.ID) == Item.Dimensions.Unit.ID
+            && r.Get<JobBOMSummary, String>(c => c.Dimensions.UnitSize).Equals(Item.Dimensions.UnitSize)
+        ).ToArray();
+        if (SelectedRows.Any())
+            ScrollIntoView(SelectedRows.First());
+    }
+    
+    protected override DynamicGridColumns LoadColumns()
+    {
+        var columns = new DynamicGridColumns();
+        columns.Add<JobBOMSummary, string>(x => x.Product.Group.Code, 120, "Group", "", Alignment.MiddleCenter);
+        columns.Add<JobBOMSummary, string>(x => x.Product.Code, 120, "Code", "", Alignment.MiddleCenter);
+        columns.Add<JobBOMSummary, string>(x => x.Product.Name, 0, "Name", "", Alignment.MiddleLeft);
+        columns.Add<JobBOMSummary, string>(x => x.Dimensions.UnitSize, 120, "Size", "", Alignment.MiddleCenter);
+        columns.Add<JobBOMSummary, string>(x => x.Style.Description, 150, "Style", "", Alignment.MiddleCenter);
+        columns.Add<JobBOMSummary, double>(x => x.BOM, 100, "BOM", "F2", Alignment.MiddleCenter);
+        columns.Add<JobBOMSummary, double>(x => x.Issued, 100, "Issued", "F2", Alignment.MiddleCenter);
+        columns.Add<JobBOMSummary, double>(x => x.Quantity, 100, "Quantity", "F2", Alignment.MiddleCenter);
+        return columns;
+    }
+
+    protected override void Changed()
+    {
+        base.Changed();
+        if (Item.ID != Guid.Empty && SelectedRows.Any())
+        {
+            foreach (var row in Data.Rows.Where(x=>x.Index != SelectedRows[0].Index))
+            {
+                if (!Items[row.Index].Quantity.IsEffectivelyEqual(0.0))
+                {
+                    row.Set<JobBOMSummary, double>(x => x.Quantity, 0.0);
+                    Items[row.Index].Quantity = 0.0;
+                }
+            }
+            InvalidateGrid();
+        }
+    }
+}

+ 14 - 1
prs.desktop/Panels/Requisitions/RequisitionItemEditor/RequisitionItemEditor.xaml

@@ -19,7 +19,8 @@
             x:Name="TabControl"
             Grid.Row="0" 
             SelectionChanged="TabChanged"
-            Margin="5,5,5,0">
+            Margin="5,5,5,0"
+            SeparatorMargin="4">
         
             <dg:DynamicTabControl.Items>
                 
@@ -230,6 +231,18 @@
                     </dg:DynamicSplitPanel>
                 </dg:DynamicTabItem>
                 
+                <dg:DynamicTabItem
+                    x:Name="BOMPage" 
+                    Header="Bill Of Materials">
+                    <local:RequisitionItemBillOfMaterialsPage x:Name="BOMItems" OnChanged="BOMItems_OnOnChanged" />
+                </dg:DynamicTabItem>
+                
+                <dg:DynamicTabItem
+                    x:Name="RequisitionPage"
+                    Header="Requisitions">
+                    <local:RequisitionItemJobRequitionPage x:Name="JobRequisitionItemsGrid" OnChanged="JobRequisitionItems_OnOnChanged" />
+                </dg:DynamicTabItem>
+                
                 <dg:DynamicTabItem x:Name="InstancesPage" Header="Catalogue">
                     <dg:DynamicSplitPanel View="Combined" AllowableViews="Combined" Anchor="Master" AnchorWidth="600">
                         <dg:DynamicSplitPanel.Header>

+ 165 - 8
prs.desktop/Panels/Requisitions/RequisitionItemEditor/RequisitionItemEditor.xaml.cs

@@ -22,6 +22,8 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
 
     private bool _loadedStockHoldings;
     private bool _loadedProductInstances;
+    private bool _loadedBOMItems;
+    private bool _loadedJobRequisitionItems;
     private bool _loadedCustom;
 
     private bool _allowActual;
@@ -37,22 +39,34 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
         }
     }
 
-    public RequisitionItemEditor(List<Product> products, RequisitionItem requisitionItem, bool allowActual)
+    public RequisitionItemEditor(Func<List<Product>> products, RequisitionItem requisitionItem, bool allowActual)
     {
         _allowActual = allowActual;
         
         InitializeComponent();
 
-        StockHoldingProducts.Items = products;
-        ProductInstancesProducts.Items = products;
+        StockHoldingProducts.ProductsFunction = products;
+        ProductInstancesProducts.ProductsFunction = products;
+        BOMItems.Item = requisitionItem;
+        JobRequisitionItemsGrid.Item = requisitionItem;
 
+        HoldingsPage.Visibility = _allowActual ? Visibility.Visible : Visibility.Collapsed;
+        InstancesPage.Visibility = _allowActual ? Visibility.Collapsed : Visibility.Visible;
+        BOMPage.Visibility = _allowActual 
+            ? Visibility.Collapsed 
+            : Visibility.Visible;
+        RequisitionPage.Visibility = _allowActual 
+            ? Visibility.Collapsed 
+            : Visibility.Visible;
+        
         Item = requisitionItem;
         TabControl.SelectedItem  = requisitionItem.EditType == RequisitionItemEditType.Normal 
-            ? HoldingsPage 
+            ? _allowActual 
+                ? HoldingsPage
+                : requisitionItem.JobRequisitionItem.ID == Guid.Empty
+                    ? BOMPage
+                    : RequisitionPage
             : CustomPage;
-
-        InstancesPage.Visibility = _allowActual ? Visibility.Collapsed : Visibility.Visible;
-
         CanSave = false;
         UpdateState();
     }
@@ -68,6 +82,23 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
                 _loadedStockHoldings = true;
             }
         }
+        else if(TabControl.SelectedTab == BOMPage)
+        {
+            if (!_loadedBOMItems)
+            {
+                LoadBOMItems();
+                _loadedBOMItems = true;
+            }
+        }
+            
+        else if(TabControl.SelectedTab == RequisitionPage)
+        {
+            if (!_loadedJobRequisitionItems)
+            {
+                LoadJobRequisitionItems();
+                _loadedJobRequisitionItems = true;
+            }
+        }
         else if(TabControl.SelectedTab == InstancesPage)
         {
             if (!_loadedProductInstances)
@@ -91,6 +122,10 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
     {
         if (TabControl.SelectedTab == HoldingsPage)
             CanSave = CanSaveStockHoldings();
+        else if (TabControl.SelectedTab == BOMPage)
+            CanSave = CanSaveBOMItems();
+        else if (TabControl.SelectedTab == RequisitionPage)
+            CanSave = CanSaveJobRequisitionItems();
         else if (TabControl.SelectedTab == InstancesPage)
             CanSave = CanSaveProductInstances();
         else if (TabControl.SelectedTab == CustomPage)
@@ -101,6 +136,10 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
     {
         if (TabControl.SelectedTab == HoldingsPage)
             SaveStockHoldings();
+        else if (TabControl.SelectedTab == BOMPage)
+            SaveBOMItems();
+        else if (TabControl.SelectedTab == RequisitionPage)
+            SaveJobRequisitionItems();
         else if (TabControl.SelectedTab == InstancesPage)
             SaveProductInstances();
         else if (TabControl.SelectedTab == CustomPage)
@@ -398,6 +437,123 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
 
     #endregion
 
+    #region BOM Items
+
+    private void LoadBOMItems()
+    {
+        BOMItems.Refresh(true,true);
+    }
+    
+    private bool CanSaveBOMItems()
+    {
+        return BOMItems.Data.Rows.Any(r =>!r.Get<JobBOMSummary,double>(x=>x.Quantity).IsEffectivelyEqual(0.0));
+    }
+
+    private void SaveBOMItems()
+    {
+        var result = new List<RequisitionItem>() { Item };
+        
+        var bomItems = BOMItems.Data.Rows
+            .Where(r => !r.Get<JobBOMSummary, double>(x => x.Quantity).IsEffectivelyEqual(0.0))
+            .Select(r => r.ToObject<JobBOMSummary>()).ToArray();
+        
+        var first = bomItems.FirstOrDefault();
+        if (first is null)
+            return;
+        
+        Item.Product.CopyFrom(first.Product);
+        Item.Style.CopyFrom(first.Style);
+        Item.Dimensions.CopyFrom(first.Dimensions, true);
+        Item.EditType = RequisitionItemEditType.Normal;
+        Item.Quantity = first.Quantity;
+
+        foreach (var item in bomItems.Skip(1).ToArray())
+        {
+            var newItem = new RequisitionItem();
+            newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
+            newItem.Done = false;
+            newItem.Description = item.Product.Name;
+            newItem.Product.CopyFrom(item.Product);
+            newItem.Style.CopyFrom(item.Style);
+            newItem.Dimensions.CopyFrom(item.Dimensions, true);
+            newItem.EditType = RequisitionItemEditType.Normal;
+            newItem.Quantity = item.Quantity;
+            result.Add(newItem);
+        }
+
+        Client.Save(result, "Edited by user.");
+
+        Result = result.ToArray();
+        
+    }
+
+    private void BOMItems_OnOnChanged(object? sender, EventArgs e)
+    {
+        CheckCanSave();
+    }
+    
+    #endregion
+    
+    #region Job Requisition Items
+
+    private void LoadJobRequisitionItems()
+    {
+        JobRequisitionItemsGrid.Refresh(true,true);
+    }
+    
+    private bool CanSaveJobRequisitionItems()
+    {
+        return JobRequisitionItemsGrid.Data.Rows.Any(r =>!r.Get<JobJRISummary,double>(x=>x.Quantity).IsEffectivelyEqual(0.0));
+    }
+
+    private void SaveJobRequisitionItems()
+    {
+        var result = new List<RequisitionItem>() { Item };
+        
+        var items = JobRequisitionItemsGrid.Data.Rows
+            .Where(r => !r.Get<JobJRISummary, double>(x => x.Quantity).IsEffectivelyEqual(0.0))
+            .Select(r => r.ToObject<JobJRISummary>()).ToArray();
+        
+        var first = items.FirstOrDefault();
+        if (first is null)
+            return;
+        
+        Item.Product.CopyFrom(first.JRI.Product);
+        Item.Style.CopyFrom(first.JRI.Style);
+        Item.Dimensions.CopyFrom(first.JRI.Dimensions, true);
+        Item.JobRequisitionItem.CopyFrom(first.JRI);
+        Item.EditType = RequisitionItemEditType.Normal;
+        Item.Quantity = first.Quantity;
+
+        foreach (var item in items.Skip(1).ToArray())
+        {
+            var newItem = new RequisitionItem();
+            newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
+            newItem.Done = false;
+            newItem.Description = item.JRI.Product.Name;
+            newItem.Product.CopyFrom(item.JRI.Product);
+            newItem.Style.CopyFrom(item.JRI.Style);
+            newItem.Dimensions.CopyFrom(item.JRI.Dimensions, true);
+            newItem.JobRequisitionItem.CopyFrom(item.JRI);
+            newItem.EditType = RequisitionItemEditType.Normal;
+            newItem.Quantity = item.Quantity;
+            result.Add(newItem);
+        }
+
+        Client.Save(result, "Edited by user.");
+
+        Result = result.ToArray();
+        
+    }
+
+    private void JobRequisitionItems_OnOnChanged(object? sender, EventArgs e)
+    {
+        CheckCanSave();
+    }
+    
+    #endregion
+
+    
     #region Product Instances
 
     private ProductInstance? SelectedProductInstance;
@@ -418,7 +574,8 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
             Dispatcher.BeginInvoke(() =>
             {
                 ProductInstancesProducts.SelectedRows = ProductInstancesProducts.Data.Rows.Where(x => x.Get<Product, Guid>(x => x.ID) == Item.Product.ID).ToArray();
-                ProductInstancesProducts.ScrollIntoView(StockHoldingProducts.SelectedRows[0]);
+                if (ProductInstancesProducts.SelectedRows.Any())
+                    ProductInstancesProducts.ScrollIntoView(ProductInstancesProducts.SelectedRows[0]);
             });
         }
     }

+ 192 - 0
prs.desktop/Panels/Requisitions/RequisitionItemEditor/RequisitionItemJobRequisitionItemPage.cs

@@ -0,0 +1,192 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Media;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace PRSDesktop.Panels.Requisitions;
+
+public class JobJRISummary : BaseObject
+{
+    public JobRequisitionItemLink JRI { get; set; }
+
+    [DoubleEditor(Editable = Editable.Disabled)]
+    public double Required { get; set; }
+    
+    [DoubleEditor(Editable = Editable.Disabled)]
+    public double Issued { get; set; }
+    
+    [DoubleEditor]
+    public double Quantity { get; set; }
+}
+
+public class RequisitionItemJobRequitionPage : DynamicItemsListGrid<JobJRISummary>, ISpecificGrid
+{
+    
+    public RequisitionItem Item { get; set; }
+    
+    private class RequisitionItemJobRequisitionPageUI : DynamicGridGridUIComponent<JobJRISummary>
+    {
+        protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
+        {
+            if (!row.Get<JobJRISummary, double>(c => c.Quantity).IsEffectivelyEqual(0.0))
+                return new SolidColorBrush(Colors.LightGreen);
+            return base.GetCellBackground(row, column);
+        }
+    }
+
+    protected override IDynamicGridUIComponent<JobJRISummary> CreateUIComponent() => new RequisitionItemJobRequisitionPageUI() { Parent = this };
+
+    protected override void Init()
+    {
+        base.Init();
+        HiddenColumns.Add(x =>x.JRI.ID);
+        HiddenColumns.Add(x=>x.JRI.Product.ID);
+        HiddenColumns.Add(x => x.JRI.Product.Group.ID);
+        HiddenColumns.Add(x=>x.JRI.Style.ID);
+        HiddenColumns.Add(x=>x.JRI. Dimensions.Unit.ID);
+        HiddenColumns.Add(x=>x.JRI.Dimensions.UnitSize);
+        HiddenColumns.Add(x => x.JRI.Qty);
+        HiddenColumns.Add(x=>x.Quantity);
+    }
+
+    protected override void DoReconfigure(FluentList<DynamicGridOption> options)
+    {
+        base.DoReconfigure(options);
+        options.BeginUpdate()
+            .Clear()
+            .Add(DynamicGridOption.FilterRows)
+            .Add(DynamicGridOption.DirectEdit)
+            .Add(DynamicGridOption.HideDirectEditButton)
+            .EndUpdate();
+    }
+
+    protected override void Reload(Filters<JobJRISummary> criteria, Columns<JobJRISummary> columns, ref SortOrder<JobJRISummary>? sort, Action<CoreTable?, Exception?> action)
+    {
+        Items.Clear();
+        var table = new CoreTable();
+        table.LoadColumns(columns);
+
+        Filters<JobRequisitionItem> itemfilters = new Filters<JobRequisitionItem>();
+        itemfilters.Add(
+            new Filter<JobRequisitionItem>(x => x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID));
+        foreach (var filter in criteria.Items)
+        {
+            var itemfilter = Serialization.Deserialize<Filter<JobRequisitionItem>>(Serialization.Serialize(filter));
+            if (itemfilter is not null)
+                itemfilters.Add(itemfilter);
+        }
+
+        Columns<JobRequisitionItem> itemcolumns = new Columns<JobRequisitionItem>();
+        foreach (var column in columns)
+            itemcolumns.Add(column.Property.Replace("JRI.",""));
+        
+        SortOrder<JobRequisitionItem>? itemsort =
+            Serialization.Deserialize<SortOrder<JobRequisitionItem>>(Serialization.Serialize(sort));
+
+        MultiQuery query = new();
+        query.Add<JobRequisitionItem>(
+            itemfilters.Combine(), 
+            itemcolumns, 
+            itemsort
+        );
+        query.Add<StockMovement>(
+            new Filter<StockMovement>(x=>x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID)
+                .And(x=>x.Type).IsEqualTo(StockMovementType.Issue),
+            new Columns<StockMovement>(x=>x.JobRequisitionItem.ID).Add(x=>x.Issued)
+        );    
+        
+        query.Query(q =>
+        {
+            try
+            {
+                var mvmts = q.Get<StockMovement>()?.Rows.Select(r => r.ToObject<StockMovement>()).ToArray();
+                var _jobRequisitionItems = q.Get<JobRequisitionItem>()?.Rows.Select(r => r.ToObject<JobRequisitionItem>()).ToArray();
+
+                if (mvmts is not null && _jobRequisitionItems is not null)
+                {
+
+                    foreach (var item in _jobRequisitionItems)
+                    {
+
+                        JobJRISummary summary = new JobJRISummary();
+                        summary.JRI.ID = item.ID;
+                        summary.JRI.Synchronise(item);
+                        
+                        summary.Required = item.Qty;
+                        
+                        var issues = mvmts.Where(x => x.JobRequisitionItem.ID == item.ID);
+                        summary.Issued = issues.Sum(x => x.Issued);
+
+                        if (summary.JRI.ID == Item.JobRequisitionItem.ID)
+                            summary.Quantity = Item.Quantity;
+                        
+                        Items.Add(summary);
+                    }
+                
+                    table.LoadRows(Items);
+                }
+
+                action(table, null);
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e);
+
+            }
+            
+        });
+        
+    }
+
+    private bool bFirst = true;
+
+    protected override void OnAfterRefresh()
+    {
+        base.OnAfterRefresh();
+        if (!bFirst)
+            return;
+        bFirst = false;
+        
+        SelectedRows = Data.Rows.Where(r => 
+            r.Get<JobJRISummary, Guid>(c => c.JRI.ID) == Item.JobRequisitionItem.ID
+        ).ToArray();
+        if (SelectedRows.Any())
+            ScrollIntoView(SelectedRows.First());
+    }
+    
+    protected override DynamicGridColumns LoadColumns()
+    {
+        var columns = new DynamicGridColumns();
+        columns.Add<JobJRISummary, int>(x => x.JRI.Requisition.Number, 50, "#", "", Alignment.MiddleCenter);
+        columns.Add<JobJRISummary, string>(x => x.JRI.Requisition.Description, 200, "Requisition", "", Alignment.MiddleLeft);
+        columns.Add<JobJRISummary, string>(x => x.JRI.Product.Code, 120, "Code", "", Alignment.MiddleCenter);
+        columns.Add<JobJRISummary, string>(x => x.JRI.Product.Name, 0, "Name", "", Alignment.MiddleLeft);
+        columns.Add<JobJRISummary, string>(x => x.JRI.Dimensions.UnitSize, 120, "Size", "", Alignment.MiddleCenter);
+        columns.Add<JobJRISummary, string>(x => x.JRI.Style.Description, 150, "Style", "", Alignment.MiddleCenter);
+        columns.Add<JobJRISummary, double>(x => x.Required, 100, "Required", "F2", Alignment.MiddleCenter);
+        columns.Add<JobJRISummary, double>(x => x.Issued, 100, "Issued", "F2", Alignment.MiddleCenter);
+        columns.Add<JobJRISummary, double>(x => x.Quantity, 100, "Quantity", "F2", Alignment.MiddleCenter);
+        return columns;
+    }
+
+    protected override void Changed()
+    {
+        base.Changed();
+        if (Item.ID != Guid.Empty && SelectedRows.Any())
+        {
+            foreach (var row in Data.Rows.Where(x=>x.Index != SelectedRows[0].Index))
+            {
+                if (!Items[row.Index].Quantity.IsEffectivelyEqual(0.0))
+                {
+                    row.Set<JobJRISummary, double>(x => x.Quantity, 0.0);
+                    Items[row.Index].Quantity = 0.0;
+                }
+            }
+            InvalidateGrid();
+        }
+    }
+}

+ 8 - 0
prs.desktop/Panels/Requisitions/RequisitionItemEditor/RequisitionItemProductSelectionGrid.cs

@@ -11,6 +11,8 @@ namespace PRSDesktop.Panels.Requisitions;
 
 public class RequisitionItemProductSelectionGrid : DynamicItemsListGrid<Product>, ISpecificGrid
 {
+    public Func<List<Product>>? ProductsFunction { get; set; }
+    
     protected override void Init()
     {
         base.Init();
@@ -24,6 +26,12 @@ public class RequisitionItemProductSelectionGrid : DynamicItemsListGrid<Product>
         options.Clear().Add(DynamicGridOption.FilterRows);
     }
 
+    protected override void Reload(Filters<Product> criteria, Columns<Product> columns, ref SortOrder<Product>? sort, Action<CoreTable?, Exception?> action)
+    {
+        Items = ProductsFunction?.Invoke() ?? new List<Product>();
+        base.Reload(criteria, columns, ref sort, action);
+    }
+
     protected override DynamicGridColumns LoadColumns()
     {
         var columns = new DynamicGridColumns();

+ 1 - 1
prs.desktop/Panels/Requisitions/RequisitionItemGrid.cs

@@ -226,7 +226,7 @@ public class RequisitionItemGrid : DynamicDataGrid<RequisitionItem>
             item = CreateItem();
         }
 
-        var editor = new RequisitionItemEditor(_products.Result, item, true);
+        var editor = new RequisitionItemEditor(() => _products.Result, item, true);
         if(editor.ShowDialog() == true && editor.Result is not null)
         {
             Refresh(false, true);

+ 1 - 0
prs.desktop/Panels/StockSummary/StockSummaryGrid.cs

@@ -218,6 +218,7 @@ public class StockSummaryGrid : DynamicDataGrid<ProductInstance>, IDataModelSour
             .Add(DynamicGridOption.FilterRows)
             .Add(DynamicGridOption.ExportData)
             .Add(DynamicGridOption.MultiSelect)
+            .Add(DynamicGridOption.HideDatabaseFilters)
             .EndUpdate();
     }