Browse Source

Merge remote-tracking branch 'origin/frank' into kenric

Kenric Nugteren 1 year ago
parent
commit
3b9719f822

+ 14 - 2
prs.classes/Entities/Job/Requisitions/JobRequisitionItem.cs

@@ -219,7 +219,7 @@ namespace Comal.Classes
         {
             public override IComplexFormulaNode<JobRequisitionItem, double> GetFormula() =>
                 Aggregate<RequisitionItem>(AggregateCalculation.Sum, x => x.Property(x => x.Quantity), new Filter<RequisitionItem>(x => x.RequisitionLink.StockUpdated).IsEqualTo(DateTime.MinValue))
-                .WithLink(x => x.SourceJRI.ID, x => x.ID);
+                .WithLink(x => x.JobRequisitionItem.ID, x => x.ID);
         }
         [ComplexFormula(typeof(PickRequestedFormula))]
         [DoubleEditor(Editable = Editable.Disabled)]
@@ -241,6 +241,16 @@ namespace Comal.Classes
         [EditorSequence(17)]
         public double Issued { get; set; }
         
+                
+        private class TotalIssuedFormula : ComplexFormulaGenerator<JobRequisitionItem, double>
+        {
+            public override IComplexFormulaNode<JobRequisitionItem, double> GetFormula() =>
+                Formula(FormulaOperator.Multiply, Property(x => x.Issued), Property(x => x.Dimensions.Value));
+        }
+        [DoubleEditor(Editable = Editable.Hidden)]
+        [ComplexFormula(typeof(TotalIssuedFormula))]
+        public double TotalIssued { get; set; }
+        
         private class TotalAllocatedFormula : ComplexFormulaGenerator<JobRequisitionItem, double>
         {
             public override IComplexFormulaNode<JobRequisitionItem, double> GetFormula() =>
@@ -256,7 +266,9 @@ namespace Comal.Classes
                 If<JobRequisitionItem, double, bool>(
                         Property<JobRequisitionItem, double>(x => x.TotalQty)
                         , Condition.GreaterThan,
-                        Property<JobRequisitionItem, double>(x => x.TotalAllocated))
+                        Formula(FormulaOperator.Add, 
+                            Property<JobRequisitionItem, double>(x => x.TotalAllocated), 
+                            Property<JobRequisitionItem, double>(x => x.TotalIssued)))
                     .Then(Constant(true))
                     .Else(Constant(false));
             //Formula(FormulaOperator.Subtract, Property(x => x.InStock), Property(x => x.Allocated));

+ 1 - 1
prs.classes/Entities/Job/Requisitions/JobRequisitionLink.cs

@@ -13,7 +13,7 @@ namespace Comal.Classes
 
         public JobLink Job { get; set; }
 
-        [IntegerEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
+        [IntegerEditor(Visible = Visible.Default, Editable = Editable.Hidden)]
         public int Number { get; set; }
 
         [TextBoxEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]

+ 2 - 2
prs.classes/Entities/Requisition/RequisitionItem.cs

@@ -93,9 +93,9 @@ namespace Comal.Classes
         public double Cost { get; set; }
         
         /// <summary>
-        /// This is the JRI that the stock for the Requisition is sourced from, filled if generated <i>from</i> a JRI. This can't be changed.
+        /// This is the JRI that the stock for the Requisition is sourced from, filled if generated <i>from</i> a JRI.
+        /// This can't be changed.
         /// </summary>
-        [NullEditor]
         [RequiredColumn]
         public JobRequisitionItemLink SourceJRI { get; set; }
         

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

@@ -241,6 +241,9 @@ namespace Comal.Classes
         {
             columns ??= Columns.None<JobRequisitionItem>();
             columns.Add(x => x.ID)
+                .Add(x => x.Product.ID)
+                .Add(x => x.Style.ID)
+                .AddDimensionsColumns(x => x.Dimensions)
                 .Add(x => x.Job.ID)
                 .Add(x => x.Job.JobNumber)
                 .Add(x => x.Job.Name)
@@ -251,7 +254,8 @@ namespace Comal.Classes
             var items = new Client<JobRequisitionItem>().Query(
                     new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
                     columns)
-                .ToObjects<JobRequisitionItem>();
+                .ToObjects<JobRequisitionItem>()
+                .Where(x=>x.Product.ID == holding.Product.ID && x.Style.ID == holding.Style.ID && x.Dimensions.Equals(holding.Dimensions));
             if (holding.Available > 0 || alwaysshowunallocated)
             {
                 var requi = new JobRequisitionItem() { Qty = holding.Available };

+ 20 - 7
prs.desktop/DockPanels/Problems/ProblemsDockGrid.cs

@@ -21,13 +21,16 @@ public class ProblemsDockGrid : DynamicDataGrid<Problems>
         base.Init();
         HiddenColumns.Add(x=>x.Problem.Notes);
         HiddenColumns.Add(x=>x.Problem.AssignedTo.Code);
+        
+        
         ActionColumns.Add(new DynamicImageColumn(TypeImage)
         {
             Position = DynamicActionColumnPosition.Start, 
             Filters = IMAGES.Keys.Select(x=>x.Name.Split('.').Last()).ToArray(),
-            FilterRecord = TypeFilter
-
+            GetFilterExpression = (col) => TypeExpression(col),
         });
+        
+
         ActionColumns.Add(new DynamicTextColumn(GetLastNote) { Position =  DynamicActionColumnPosition.End, Width = 0, HeaderText = "Issues" });
         ActionColumns.Add(new DynamicTextColumn(GetAssignedTo) { Position =  DynamicActionColumnPosition.End, Width = 100, HeaderText="Assigned To", Alignment = Alignment.MiddleCenter});
         ActionColumns.Add(new DynamicMenuColumn(ProblemMenu) { Position = DynamicActionColumnPosition.End });
@@ -37,7 +40,12 @@ public class ProblemsDockGrid : DynamicDataGrid<Problems>
         AddButton("Mark Resolved", PRSDesktop.Resources.delete.AsBitmapImage(), MarkResolved,
             position: DynamicGridButtonPosition.Right);
     }
-
+    
+    private string TypeExpression(DynamicActionColumn column)
+    {
+        return column.CreateFilterExpression("Type") ?? "";
+    }
+    
     private bool TypeFilter(CoreRow row, string[] filter)
     {
         string typename = row.Get<Problems, string>(x => x.Type);
@@ -85,13 +93,18 @@ public class ProblemsDockGrid : DynamicDataGrid<Problems>
             ? IMAGES[_key]
             : null;
     }
+    
+    private string? TypeText(CoreRow? row)
+    {
+        return row?.Get<Problems, string>(x => x.Type) ?? "";
+    }
 
     private void ProblemMenu(DynamicMenuColumn menu, CoreRow? row)
     {
         menu.AddItem("Add Note", null, AddNote);
         menu.AddItem("Assign To...", null, AssignTo);
         menu.AddSeparator();
-        menu.AddItem("Edit Item", null, EitItem);
+        menu.AddItem("Edit Item", null, EditItem);
         menu.AddSeparator();
         menu.AddItem("Mark as Resolved", null, MarkResolved);
     }
@@ -266,10 +279,10 @@ public class ProblemsDockGrid : DynamicDataGrid<Problems>
         base.DoDoubleClick(sender, args);
         if (args.Row == null)
             return;
-        EitItem(args.Row);
+        EditItem(args.Row);
     }
 
-    private void EitItem(CoreRow? row)
+    private void EditItem(CoreRow? row)
     {
         var _type = GetType(row);
         if (_type == null)
@@ -278,7 +291,7 @@ public class ProblemsDockGrid : DynamicDataGrid<Problems>
         if (_item == null)
             return;
         var _grid = DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), _type);
-        if (_grid.EditItems([ _item ]))
+        if (_grid.EditItems([ _item ], null, false, this))
             Refresh(false,true);
     }
 }

+ 17 - 1
prs.desktop/Grids/ProductDimensionUnitGrid.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Windows;
+using System.Windows.Media.Imaging;
 using Comal.Classes;
 using InABox.Core;
 using InABox.DynamicGrid;
@@ -12,6 +13,9 @@ namespace PRSDesktop.Grids;
 
 public class ProductDimensionUnitGrid : DynamicDataGrid<ProductDimensionUnit>
 {
+
+    private static readonly BitmapImage CONVERT = PRSDesktop.Resources.specifications.AsBitmapImage();
+    
     protected override void Init()
     {
         base.Init();
@@ -22,7 +26,10 @@ public class ProductDimensionUnitGrid : DynamicDataGrid<ProductDimensionUnit>
         HiddenColumns.Add(x => x.HasWeight);
         HiddenColumns.Add(x => x.HasWidth);
         HiddenColumns.Add(x => x.HasHeight);
-
+        HiddenColumns.Add(x =>x.Conversion);
+        
+        ActionColumns.Add(new DynamicImageColumn(ConvertImage) { Position = DynamicActionColumnPosition.End});
+        
         AddButton("Update Expressions", null, (button, rows) =>
         {
             UpdateExpressions(rows.ToArray<ProductDimensionUnit>());
@@ -30,6 +37,15 @@ public class ProductDimensionUnitGrid : DynamicDataGrid<ProductDimensionUnit>
         });
     }
 
+    private BitmapImage? ConvertImage(CoreRow? row)
+    {
+        return row == null
+            ? CONVERT
+            : String.IsNullOrWhiteSpace(row.Get<ProductDimensionUnit, string>(x => x.Conversion))
+                ? null
+                : CONVERT;
+    }
+
     protected override void DoValidate(ProductDimensionUnit[] items, List<string> errors)
     {
         base.DoValidate(items, errors);

+ 5 - 0
prs.desktop/Panels/Products/Locations/StockHoldingGrid.cs

@@ -334,6 +334,11 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
         var holding = row.ToObject<StockHolding>();
 
         var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), typeof(JobRequisitionItem)) as DynamicDataGrid<JobRequisitionItem>)!;
+        grid.Options.SelectColumns = true;
+        grid.Options.AddRows = false;
+        grid.Options.EditRows = false;
+        grid.Options.DeleteRows = false;
+        grid.Options.MultiSelect = false;
         grid.OnDefineFilter += (type) =>
         {
             if(type == typeof(JobRequisitionItem))

+ 30 - 9
prs.desktop/Panels/Products/Locations/StockLocationPanel.xaml

@@ -10,28 +10,49 @@
     <dynamic:DynamicSplitPanel MasterCaption="Stock Location List" DetailCaption="Location Summary" AnchorWidth="500" AllowableViews="Master,Combined"
                                View="Combined" DetailHeight="400">
         <dynamic:DynamicSplitPanel.Header>
-            <Border CornerRadius="5,5,0,0" BorderBrush="Gray" BorderThickness="0.75" DockPanel.Dock="Top"
-                    Background="WhiteSmoke">
-                    <Label Content="Stock Location List" HorizontalContentAlignment="Center" DockPanel.Dock="Left" />
+            <Border 
+                BorderBrush="Gray" 
+                BorderThickness="0.75" 
+                DockPanel.Dock="Top"
+                Background="WhiteSmoke">
+                    
+                <Label 
+                    Content="Stock Location List" 
+                    HorizontalContentAlignment="Center" />
+                
             </Border>
         </dynamic:DynamicSplitPanel.Header>
+        
         <dynamic:DynamicSplitPanel.Master>
-            <local:StockLocationGrid x:Name="Locations" DockPanel.Dock="Top" Margin="0,2,0,0" />
+            <local:StockLocationGrid 
+                x:Name="Locations" 
+                DockPanel.Dock="Top" />
         </dynamic:DynamicSplitPanel.Master>
 
         <dynamic:DynamicSplitPanel.DetailHeader>
-            <Border CornerRadius="5,5,0,0" BorderBrush="Gray" BorderThickness="0.75" DockPanel.Dock="Top"
-                    Background="WhiteSmoke">
-                <Label Content="Location Summary" HorizontalContentAlignment="Center" />
+            <Border 
+                BorderBrush="Gray" 
+                BorderThickness="0.75" 
+                DockPanel.Dock="Top"
+                Background="WhiteSmoke">
+                <Label 
+                    Content="Location Summary" 
+                    HorizontalContentAlignment="Center" />
             </Border>
         </dynamic:DynamicSplitPanel.DetailHeader>
         <dynamic:DynamicSplitPanel.Detail>
-            <local:StockHoldingGrid x:Name="Holdings" DockPanel.Dock="Top" Margin="0,2,0,0" OnSelectItem="Holdings_OnOnSelectItem" />
+            <local:StockHoldingGrid 
+                x:Name="Holdings" 
+                DockPanel.Dock="Top"
+                OnSelectItem="Holdings_OnOnSelectItem" />
         </dynamic:DynamicSplitPanel.Detail>
 
         <dynamic:DynamicSplitPanel.SecondaryDetail>
             <DockPanel>
-                <Border CornerRadius="0,0,0,0" BorderBrush="Gray" BorderThickness="0.75" DockPanel.Dock="Top"
+                <Border 
+                    BorderBrush="Gray" 
+                    BorderThickness="0.75" 
+                    DockPanel.Dock="Top"
                     Background="WhiteSmoke">
                     <Grid>
                         <Grid.ColumnDefinitions>

+ 1 - 1
prs.desktop/Panels/Products/Master List/ProductHoldingControl.cs

@@ -125,9 +125,9 @@ public class ProductHoldingControl : DynamicDataGrid<StockHolding>, IProductCont
                 StockMovementBatch _batch = new StockMovementBatch()
                 {
                     Type = StockMovementBatchType.Transfer,
-                    Employee = new EmployeeLink() { ID = App.EmployeeID },
                     Notes = "Stock Value Adjustment"
                 };
+                _batch.Employee.ID = App.EmployeeID;
                 Client.Save(_batch,"Stock value adjusted from Products List");
                 
                 progress.Report("Creating Movements");

+ 51 - 20
prs.desktop/Panels/Requisitions/RequisitionItemEditor/RequisitionItemEditor.xaml.cs

@@ -188,7 +188,7 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
 
     #region Allocations
     
-    private IEnumerable<StockSelectionItem> CreateStockSelectionItems(IEnumerable<IGrouping<Guid, StockMovement>> groups, Func<StockMovement,string> description)
+    private IEnumerable<StockSelectionItem> CreateStockSelectionItems(IEnumerable<IGrouping<Tuple<Guid,Guid>, StockMovement>> groups, Tuple<Guid,Guid,double>[] values, Func<StockMovement,string> description)
     {
         
         var _result = new List<StockSelectionItem>();
@@ -198,15 +198,20 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
             if (!_units.IsEffectivelyEqual(0.0))
             {
                 var _holding = new StockHolding();
-                _holding.Location.ID = _group.Key;
+                _holding.Location.ID = _group.Key.Item1;
+                _holding.Job.ID = _group.Key.Item2;
                 _holding.Location.Synchronise(_group.FirstOrDefault()?.Location ?? new StockLocationLink());
                 _holding.Product.ID = Item.Product.ID;
                 _holding.Product.Synchronise(Item.Product);
                 _holding.Style.ID = Item.Style.ID;
                 _holding.Style.Synchronise(Item.Style);
+                _holding.Job.CopyFrom(_group.FirstOrDefault()?.Job ?? new JobLink());
                 _holding.Dimensions.CopyFrom(Item.Dimensions);
                 _holding.Location.Area.Code = description(_group.FirstOrDefault() ?? new StockMovement());
                 _holding.Units = _units;
+                
+                _holding.AverageValue = values.FirstOrDefault(x=>x.Item1 == _group.Key.Item1 && x.Item2 == _group.Key.Item2)?.Item3 ?? 0.0;
+
                 var _jri = new JobRequisitionItem()
                 {
                     ID = _group.FirstOrDefault()?.JobRequisitionItem.ID ?? Guid.Empty
@@ -219,12 +224,13 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
     
     private void LoadAllocations()
     {
-        var movements = Client.Query(
-            new Filter<StockMovement>(x=>x.Product.ID).IsEqualTo(Item.Product.ID)
+        MultiQuery query = new MultiQuery();
+        
+        query.Add(new Filter<StockMovement>(x=>x.Product.ID).IsEqualTo(Item.Product.ID)
                 .And(x=>x.Style.ID).IsEqualTo(Item.Style.ID)
                 .And(x=>x.Dimensions).DimensionEquals(Item.Dimensions)
                 .And(
-                    new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Item.SourceJRI.ID)
+                    new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Item.JobRequisitionItem.ID)
                         .Or(new Filter<StockMovement>(x => x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID)
                                 .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty))
                         .Or(new Filter<StockMovement>(x => x.Job.ID).IsEqualTo(Guid.Empty)
@@ -241,15 +247,29 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
                 .Add(x=>x.JobRequisitionItem.Requisition.Number)
                 .Add(x=>x.JobRequisitionItem.Requisition.Description)
                 .Add(x => x.Units)
-        ).Rows.Select(x=>x.ToObject<StockMovement>()).ToArray();
+        );
+        
+        query.Add(new Filter<StockHolding>(x=>x.Product.ID).IsEqualTo(Item.Product.ID)
+                .And(x=>x.Style.ID).IsEqualTo(Item.Style.ID)
+                .And(x=>x.Dimensions).DimensionEquals(Item.Dimensions),
+            Columns.None<StockHolding>()
+                .Add(x => x.Location.ID)
+                .Add(x => x.Job.ID)
+                .Add(x => x.AverageValue)
+        );
+        
+        query.Query();
+        var values = query.Get<StockHolding>().ToTuples<StockHolding,Guid,Guid,double>(x=>x.Location.ID, x=>x.Job.ID, x=>x.AverageValue).ToArray();
+        var movements = query.Get<StockMovement>().ToArray<StockMovement>();
         
         List<StockSelectionItem> items = new();
         
-        var allocations = movements.Where(x => x.JobRequisitionItem.ID == Item.SourceJRI.ID)
-            .GroupBy(x => x.Location.ID);
+        var allocations = movements.Where(x => x.JobRequisitionItem.ID == Item.JobRequisitionItem.ID)
+            .GroupBy(x => new Tuple<Guid,Guid>(x.Location.ID,x.Job.ID));
         items.AddRange(
             CreateStockSelectionItems(
-                allocations, 
+                allocations,
+                values,
                 (m) => $"{m.Job.JobNumber}:{m.JobRequisitionItem.Requisition.Number} {m.JobRequisitionItem.Requisition.Description}"
             )
         );
@@ -258,19 +278,21 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
         var reserves = movements.Where(x =>
                 x.JobRequisitionItem.ID != Guid.Empty && x.Job.ID == Item.RequisitionLink.JobLink.ID &&
                 x.JobRequisitionItem.ID == Guid.Empty)
-            .GroupBy(x => x.Location.ID);
+            .GroupBy(x => new Tuple<Guid,Guid>(x.Location.ID, x.Job.ID));
         items.AddRange(
             CreateStockSelectionItems(
                 reserves, 
+                values,
                 (m) => $"{m.Job.JobNumber} (Unallocated Stock)"
             )
         );
         
         var freeitems = movements.Where(x => x.Job.ID == Guid.Empty && x.JobRequisitionItem.ID == Guid.Empty)
-            .GroupBy(x => x.Location.ID);
+            .GroupBy(x => new Tuple<Guid,Guid>(x.Location.ID,x.Job.ID));
         items.AddRange(
             CreateStockSelectionItems(
-                freeitems, 
+                freeitems,
+                values,
                 (m) => $"General Stock"
             )
         );
@@ -331,15 +353,15 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
             var newItem = newItems.Count == 0 ? Item : CopyRequisitionItem();
             newItem.Location.CopyFrom(item.Holding.Location);
             newItem.Cost = item.Holding.AverageValue;
-            newItem.JobRequisitionItem.CopyFrom(item.JRI);
+            newItem.SourceJRI.CopyFrom(item.JRI);
             newItem.JobLink.CopyFrom(item.Holding.Job);
             
             // Automagically attach to Requisition if it seems appropriate
             // ie selected from a JRI allocation, and current source jri is blank
-            if (newItem.SourceJRI.ID == Guid.Empty
+            if (newItem.JobRequisitionItem.ID == Guid.Empty
                 && newItem.RequisitionLink.JobLink.ID == item.Holding.Job.ID
                 && item.JRI.ID != Guid.Empty)
-                newItem.SourceJRI.ID = item.JRI.ID;
+                newItem.JobRequisitionItem.ID = item.JRI.ID;
 
             newItem.Quantity = item.Issued;
             newItem.ActualQuantity = item.Issued;
@@ -353,7 +375,7 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
         {
             var newItem = CopyRequisitionItem();
             newItem.Location.CopyFrom(new StockLocationLink());
-            newItem.JobRequisitionItem.CopyFrom(new JobRequisitionItemLink());
+            newItem.SourceJRI.CopyFrom(new JobRequisitionItemLink());
             newItem.JobLink.CopyFrom(new JobLink());
             newItem.Quantity = remainder;
             newItem.Done = false;
@@ -473,7 +495,7 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
                 newItem.Description = Item.Description;
                 newItem.Image.CopyFrom(Item.Image);
                 newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
-                newItem.JobRequisitionItem.ID = item.ID;
+                newItem.JobRequisitionItem.CopyFrom(Item.JobRequisitionItem);
                 newItem.Product.CopyFrom(Item.Product);
                 newItem.Style.CopyFrom(Item.Style);
                 newItem.Location.CopyFrom(Item.Location);
@@ -481,10 +503,19 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
                 newItem.JobLink.CopyFrom(Item.JobLink);
                 newItem.EditType = RequisitionItemEditType.Normal;
             }
-            newItem.JobRequisitionItem.ID = item.ID;
+            
+            // Automagically attach to Requisition if it seems appropriate
+            // ie selected from a JRI allocation, and current source jri is blank
+            if (newItem.JobRequisitionItem.ID == Guid.Empty
+                && newItem.RequisitionLink.JobLink.ID == StockHolding.Job.ID
+                && item.ID != Guid.Empty)
+                newItem.JobRequisitionItem.ID = item.ID;
+            
+            newItem.SourceJRI.ID = item.ID;
             newItem.Quantity = item.Taken;
             newItem.ActualQuantity = item.Taken;
             newItem.Cost = StockHolding.AverageValue;
+            newItem.Done = true;
             remainder -= item.Taken;
 
             newItems.Add(newItem);
@@ -497,8 +528,8 @@ public partial class RequisitionItemEditor : INotifyPropertyChanged
             newItem.Description = Item.Description;
             newItem.Image.CopyFrom(Item.Image);
             newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
-
-            newItem.JobRequisitionItem.ID = Guid.Empty;
+            newItem.JobRequisitionItem.CopyFrom(Item.JobRequisitionItem);
+            newItem.SourceJRI.ID = Guid.Empty;
             newItem.Product.CopyFrom(Item.Product);
             newItem.Style.CopyFrom(Item.Style);
             newItem.Dimensions.CopyFrom(Item.Dimensions, true);

+ 8 - 3
prs.desktop/Panels/Requisitions/RequisitionItemGrid.cs

@@ -98,9 +98,14 @@ public class RequisitionItemGrid : DynamicDataGrid<RequisitionItem>
             return false;
 
         var item = LoadItem(row);
-        item.Done = !item.Done;
-        item.Location.CopyFrom(new StockLocationLink());
-        item.JobRequisitionItem.CopyFrom(new JobRequisitionItemLink());
+        if (item.Done)
+        {
+            item.Done = false;
+            item.Location.CopyFrom(new StockLocationLink());
+            item.JobLink.CopyFrom(new JobLink());
+            item.SourceJRI.CopyFrom(new JobRequisitionItemLink());
+            item.ActualQuantity = 0.0;
+        }
         SaveItem(item);
         return true;
     }

+ 3 - 2
prs.desktop/Panels/Requisitions/RequisitionPanel.xaml.cs

@@ -508,8 +508,9 @@ public partial class RequisitionPanel : UserControl, IPanel<Requisition>
         if (_requisition.StockUpdated.IsEmpty())
         {
             var emptyrows = Items.Data.Rows.Where(r =>
-                !Entity.IsEntityLinkValid<RequisitionItem, StockLocationLink>(x => x.Location, r) &&
-                r.Get<RequisitionItem, bool>(c => c.Product.NonStock).Equals(false));
+                !r.Get<RequisitionItem,double>(x=>x.ActualQuantity).IsEffectivelyEqual(0.0)
+                && !Entity.IsEntityLinkValid<RequisitionItem, StockLocationLink>(x => x.Location, r) 
+                && r.Get<RequisitionItem, bool>(c => c.Product.NonStock).Equals(false));
             if (emptyrows.Any())
             {
                 MessageWindow.ShowMessage(

+ 1 - 1
prs.desktop/Panels/Reservation Management/JobRequisitionItemSelectionGrid.cs

@@ -86,7 +86,7 @@ public class JobRequisitionItemSelectionItem : BaseObject
 
     public bool CanPlus => Editable && Issued < MaxValue;
 
-    public JobRequisitionItem JRI { get; set; }
+    public JobRequisitionItem? JRI { get; set; }
 }
 
 public abstract class JobRequisitionItemSelectionGrid<T> : DynamicItemsListGrid<T>, INotifyPropertyChanged

+ 4 - 4
prs.desktop/Panels/Reservation Management/StockSelectionPage.xaml.cs

@@ -20,17 +20,17 @@ namespace PRSDesktop;
 
 public class StockSelectionItem : JobRequisitionItemSelectionItem
 {
-    public string Area => Holding.Location.Area.Code;
+    public string Area => Holding?.Location?.Area?.Code ?? "";
 
     public string Requisition => JRI is null
         ? "Unrequisitioned Items"
         : $"{JRI.Job.JobNumber}: #{JRI.Requisition.Number} ({JRI.Requisition.Description})";
 
-    public string Location => Holding.Location.Code;
+    public string Location => Holding?.Location?.Code ?? "";
 
-    public string Style => Holding.Style.Code;
+    public string Style => Holding?.Style?.Code ?? "";
 
-    public StockHolding Holding { get; set; }
+    public StockHolding? Holding { get; set; }
 }
 
 public class StockSelectionGrid : JobRequisitionItemSelectionGrid<StockSelectionItem>

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

@@ -53,5 +53,6 @@ public static class DatabaseUpdateScripts
         DataUpdater.RegisterUpdateScript<Update_8_22>();
         DataUpdater.RegisterUpdateScript<Update_8_23>();
         DataUpdater.RegisterUpdateScript<Update_8_24>();
+        DataUpdater.RegisterUpdateScript<Update_8_25>();
     }
 }

+ 59 - 0
prs.shared/Database Update Scripts/Update_8_25.cs

@@ -0,0 +1,59 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.Database;
+
+namespace PRS.Shared.Database_Update_Scripts;
+
+internal class Update_8_25 : DatabaseUpdateScript
+{
+    
+    private static readonly int CHUNK_SIZE = 500;
+    private static readonly bool DESTRUCTIVE = false;
+    
+    public override VersionNumber Version => new(8, 25);
+    
+    public override bool Update()
+    {
+        var _provider = DbFactory.NewProvider(Logger.Main);
+        CheckProblemNotes<Bill>(_provider);
+        CheckProblemNotes<Activity>(_provider);
+        CheckProblemNotes<ProductStyle>(_provider);
+        CheckProblemNotes<Product>(_provider);
+        CheckProblemNotes<CostSheet>(_provider);
+        CheckProblemNotes<Kit>(_provider);
+        CheckProblemNotes<JobBillOfMaterialsItem>(_provider);
+        CheckProblemNotes<JobRequisitionItem>(_provider);
+        CheckProblemNotes<ManufacturingPacket>(_provider);
+        return true;
+    }
+    
+    private void CheckProblemNotes<T>(IProvider provider) where T : Entity, IProblems<ManagedProblem>, new()
+    {
+        Queue<T> _updates = new Queue<T>();
+        Logger.Send(LogType.Information, "", $"Checking {typeof(T).EntityName().Split('.').Last()} Problem Notes");
+        var _problems = provider.Query<T>(
+            new Filter<T>(x=>x.Problem.Notes).IsNotEqualTo(Array.Empty<string>()), 
+            Columns.None<T>()
+                .Add(x=>x.ID)
+                .Add(x=>x.Problem.Notes)
+        ).ToObjects<T>();
+        foreach (var _problem in _problems)
+        {
+            if (_problem.Problem.Notes is string[] _notes && _notes.Length <= 1 && string.IsNullOrWhiteSpace(_notes.FirstOrDefault()))
+            {
+                _problem.Problem.Notes = null;
+                _updates.Enqueue(_problem);
+            }
+        }
+        
+        while (_updates.Any())
+        {
+            var _chunk = _updates.Dequeue(100).ToArray();
+            Logger.Send(LogType.Information, "", $"- Updating {_chunk.Length} {typeof(T).EntityName().Split('.').Last()} Problems ({_updates.Count} remaining)");
+            provider.Save(_chunk);
+        }
+        
+    }
+    
+
+}

+ 4 - 0
prs.stores/BaseStore.cs

@@ -6,6 +6,7 @@ using Comal.Classes;
 using InABox.Core;
 using InABox.Database;
 using System;
+using com.sun.org.glassfish.external.probe.provider.annotations;
 
 namespace Comal.Stores
 {
@@ -38,6 +39,9 @@ namespace Comal.Stores
         protected override void BeforeSave(T entity)
         {
             CheckPlatformVersion();
+            if (entity is IProblems problems && problems.Problem.Notes?.Length == 1 &&
+                string.IsNullOrEmpty(problems.Problem.Notes[0]))
+                problems.Problem.Notes = null;
             base.BeforeSave(entity);
         }
 

+ 4 - 3
prs.stores/RequisitionStore.cs

@@ -45,7 +45,7 @@ namespace Comal.Stores
         private bool LoadRequisitionItems(Requisition entity, ref IList<RequisitionItem> requisitionitems)
         {
             requisitionitems ??= Provider.Query(
-                    new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID),
+                    new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID).And(x=>x.ActualQuantity).IsNotEqualTo(0.0),
                     Columns.None<RequisitionItem>().Add(x => x.ID)
                         .Add(x => x.Description)
                         .Add(x => x.Quantity)
@@ -279,7 +279,8 @@ namespace Comal.Stores
             batch.Employee.ID = entity.Employee.ID;
             batch.Requisition.ID = entity.ID;
             FindSubStore<StockMovementBatch>().Save(batch, "");
-
+            
+            var timestamp = entity.Filled;
             var updates = new List<StockMovement>();
             foreach (var item in items)
             {
@@ -302,7 +303,7 @@ namespace Comal.Stores
                 }
 
                 var qty = item.ActualQuantity;
-                var timestamp = entity.Filled;
+                
                 var txnid = Guid.NewGuid();
                 if (holdingQty.IsEffectivelyLessThan(qty))
                 {