|  | @@ -24,7 +24,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |      private MovementAction _action;
 | 
	
		
			
				|  |  |      private StockHolding? _holding;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    private readonly Guid _employeeid = Guid.Empty;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private Button IssueButton;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -35,12 +35,14 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |      //Button ReserveButton = null;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private Button TransferButton;
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    private Button RecalculateButton;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public StockHoldingGrid() : base()
 | 
	
		
			
				|  |  |      {         
 | 
	
		
			
				|  |  |          ColumnsTag = "StockHolding";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        _employeeid = GetEmployeeID();
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      protected override void Init()
 | 
	
		
			
				|  |  |      {
 | 
	
	
		
			
				|  | @@ -60,6 +62,9 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |          TransferButton.Margin = new Thickness(20, TransferButton.Margin.Top, TransferButton.Margin.Right, TransferButton.Margin.Bottom);
 | 
	
		
			
				|  |  |          TransferButton.IsEnabled = false;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings,
 | 
	
		
			
				|  |  | +            DynamicGridButtonPosition.Right);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          HiddenColumns.Add(x => x.Product.ID);
 | 
	
		
			
				|  |  |          HiddenColumns.Add(x => x.Job.ID);
 | 
	
		
			
				|  |  |          HiddenColumns.Add(x => x.Job.JobNumber);
 | 
	
	
		
			
				|  | @@ -84,11 +89,152 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |          HiddenColumns.Add(x => x.Dimensions.Width);
 | 
	
		
			
				|  |  |          HiddenColumns.Add(x => x.Dimensions.Height);
 | 
	
		
			
				|  |  |          HiddenColumns.Add(x => x.Dimensions.Quantity);
 | 
	
		
			
				|  |  | -        HiddenColumns.Add(x => x.Dimensions.Value);
 | 
	
		
			
				|  |  | +        HiddenColumns.Add(x => x.Dimensions.Value);        
 | 
	
		
			
				|  |  | +        HiddenColumns.Add(x => x.Dimensions.UnitSize);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          ActionColumns.Add(new DynamicMenuColumn(BuildMenu) { Position = DynamicActionColumnPosition.End });
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        Dictionary<String, int> messages = new();
 | 
	
		
			
				|  |  | +        void AddMessage(String type)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            messages.TryGetValue(type, out int count);
 | 
	
		
			
				|  |  | +            messages[type] = ++count;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        Progress.ShowModal("Recalculating", progress =>
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            progress.Report("Loading Data");
 | 
	
		
			
				|  |  | +            MultiQuery query = new MultiQuery();
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            query.Add(
 | 
	
		
			
				|  |  | +                new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(Location.ID),
 | 
	
		
			
				|  |  | +                new Columns<StockHolding>(x => x.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Product.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Job.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Style.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Unit.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Length)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Width)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Height)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Quantity)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Value)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.UnitSize)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Units)
 | 
	
		
			
				|  |  | +                    .Add(x => x.AverageValue)            
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            query.Add(
 | 
	
		
			
				|  |  | +                new Filter<StockMovement>(x => x.Location.ID).IsEqualTo(Location.ID),
 | 
	
		
			
				|  |  | +                new Columns<StockMovement>(x => x.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Product.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Job.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Style.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Unit.ID)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Length)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Width)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Height)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Quantity)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.Value)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Dimensions.UnitSize)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Units)
 | 
	
		
			
				|  |  | +                    .Add(x => x.Cost)
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            query.Query();
 | 
	
		
			
				|  |  | +            var holdings = query.Get<StockHolding>().ToObjects<StockHolding>().ToList();
 | 
	
		
			
				|  |  | +            var movements = query.Get<StockMovement>().ToObjects<StockMovement>().ToList();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            progress.Report("Processing");
 | 
	
		
			
				|  |  | +            var updates = new List<StockHolding>();
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            while (movements.Any())
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var first = movements.First();
 | 
	
		
			
				|  |  | +                var selected = movements.Where(x =>
 | 
	
		
			
				|  |  | +                    x.Product.ID == first.Product.ID
 | 
	
		
			
				|  |  | +                    && x.Job.ID == first.Job.ID
 | 
	
		
			
				|  |  | +                    && x.Style.ID == first.Style.ID
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Unit.ID == first.Dimensions.Unit.ID
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Length.EqualsWithTolerance(first.Dimensions.Length)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Width.EqualsWithTolerance(first.Dimensions.Width)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Height.EqualsWithTolerance(first.Dimensions.Height)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Quantity.EqualsWithTolerance(first.Dimensions.Quantity)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Weight.EqualsWithTolerance(first.Dimensions.Weight)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Value.EqualsWithTolerance(first.Dimensions.Value)
 | 
	
		
			
				|  |  | +                    && String.Equals(x.Dimensions.UnitSize, first.Dimensions.UnitSize)
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +                var units = selected.Aggregate(0.0d, (t, s) => t += s.Units);
 | 
	
		
			
				|  |  | +                var cost = selected.Aggregate(0.0d, (t, s) => t += (s.Units * s.Cost));
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                var holding = holdings.FirstOrDefault(x =>
 | 
	
		
			
				|  |  | +                    x.Product.ID == first.Product.ID
 | 
	
		
			
				|  |  | +                    && x.Job.ID == first.Job.ID
 | 
	
		
			
				|  |  | +                    && x.Style.ID == first.Style.ID
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Unit.ID == first.Dimensions.Unit.ID
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Length.EqualsWithTolerance(first.Dimensions.Length)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Width.EqualsWithTolerance(first.Dimensions.Width)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Height.EqualsWithTolerance(first.Dimensions.Height)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Quantity.EqualsWithTolerance(first.Dimensions.Quantity)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Weight.EqualsWithTolerance(first.Dimensions.Weight)
 | 
	
		
			
				|  |  | +                    && x.Dimensions.Length.EqualsWithTolerance(first.Dimensions.Length)
 | 
	
		
			
				|  |  | +                    && String.Equals(x.Dimensions.UnitSize, first.Dimensions.UnitSize)
 | 
	
		
			
				|  |  | +                );
 | 
	
		
			
				|  |  | +                if (holding == null)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    holding = new StockHolding();
 | 
	
		
			
				|  |  | +                    holding.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | +                    holding.Product.ID = first.Product.ID;
 | 
	
		
			
				|  |  | +                    holding.Style.ID = first.Style.ID;
 | 
	
		
			
				|  |  | +                    holding.Job.ID = first.Job.ID;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Unit.ID = first.Dimensions.Unit.ID;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Length = first.Dimensions.Length;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Width = first.Dimensions.Width;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Height = first.Dimensions.Height;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Quantity = first.Dimensions.Quantity;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Weight = first.Dimensions.Weight;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.Value = first.Dimensions.Value;
 | 
	
		
			
				|  |  | +                    holding.Dimensions.UnitSize = first.Dimensions.UnitSize;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                holding.Units = units;
 | 
	
		
			
				|  |  | +                holding.AverageValue = units.EqualsWithTolerance(0.0F) ? 0.0d : cost / units;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (holdings.Contains(holding))
 | 
	
		
			
				|  |  | +                    holdings.Remove(holding);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (holding.IsChanged() && !holding.Units.EqualsWithTolerance(0.0f))
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    AddMessage(holding.ID != Guid.Empty ? "updated" : "added");
 | 
	
		
			
				|  |  | +                    updates.Add(holding);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                movements.RemoveAll(x => selected.Any(s => s.ID == x.ID));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            foreach (var holding in holdings)
 | 
	
		
			
				|  |  | +                AddMessage("deleted");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (updates.Any())
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                progress.Report($"Updating {updates.Count} Holdings");
 | 
	
		
			
				|  |  | +                new Client<StockHolding>().Save(updates.Where(x => x.IsChanged()), "Updated by Recalculation");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (holdings.Any())
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                progress.Report($"Deleting {holdings.Count} Holdings");
 | 
	
		
			
				|  |  | +                new Client<StockHolding>().Delete(holdings, "Removed by Recalculation");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        MessageWindow.ShowMessage(
 | 
	
		
			
				|  |  | +            messages.Any() 
 | 
	
		
			
				|  |  | +                ? String.Join("\n", messages.Select(x => $"{x.Value} holdings {x.Key}"))
 | 
	
		
			
				|  |  | +                : "Nothing to Update!"
 | 
	
		
			
				|  |  | +            ,"Recalculate");
 | 
	
		
			
				|  |  | +        return true;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      public override DynamicGridColumns GenerateColumns()
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          var columns = new DynamicGridColumns<StockHolding>();
 | 
	
	
		
			
				|  | @@ -107,14 +253,139 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |      protected override void DoReconfigure(FluentList<DynamicGridOption> options)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          base.DoReconfigure(options);
 | 
	
		
			
				|  |  | -        options.AddRange(DynamicGridOption.RecordCount, DynamicGridOption.SelectColumns, DynamicGridOption.FilterRows);
 | 
	
		
			
				|  |  | +        options
 | 
	
		
			
				|  |  | +            .BeginUpdate()
 | 
	
		
			
				|  |  | +            .Remove(DynamicGridOption.AddRows)
 | 
	
		
			
				|  |  | +            .Remove(DynamicGridOption.EditRows)
 | 
	
		
			
				|  |  | +            .Remove(DynamicGridOption.DeleteRows)
 | 
	
		
			
				|  |  | +            .Add(DynamicGridOption.RecordCount)
 | 
	
		
			
				|  |  | +            .Add(DynamicGridOption.SelectColumns)
 | 
	
		
			
				|  |  | +            .Add(DynamicGridOption.FilterRows)
 | 
	
		
			
				|  |  | +            .EndUpdate();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          if (row is null) return;
 | 
	
		
			
				|  |  | +        var holding = row.ToObject<StockHolding>();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        column.AddItem("View Requisition Items", null, ViewRequisitions_Click);
 | 
	
		
			
				|  |  | +        if (holding.Available.EqualsWithTolerance(holding.Units))
 | 
	
		
			
				|  |  | +            column.AddItem("(No Requisitions in this Holding", null, null).IsEnabled = false;
 | 
	
		
			
				|  |  | +        else
 | 
	
		
			
				|  |  | +            column.AddItem("View Requisition Items", null, ViewRequisitions_Click);
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        column.AddSeparator();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        var requiitems = new Client<JobRequisitionItem>()
 | 
	
		
			
				|  |  | +            .Query(
 | 
	
		
			
				|  |  | +                new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
 | 
	
		
			
				|  |  | +                new Columns<JobRequisitionItem>(x=>x.ID)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Job.JobNumber)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Requisition.Number)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Requisition.Description)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Qty)
 | 
	
		
			
				|  |  | +            ).ToObjects<JobRequisitionItem>()
 | 
	
		
			
				|  |  | +            .ToList();
 | 
	
		
			
				|  |  | +        if (!holding.Available.EqualsWithTolerance(0.0F))
 | 
	
		
			
				|  |  | +            requiitems.Insert(0, new JobRequisitionItem() { Qty = holding.Available });
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (requiitems.Count <= 1)
 | 
	
		
			
				|  |  | +            column.AddItem("Relocate Items", null, r => RelocateItems(holding, requiitems.ToArray()));
 | 
	
		
			
				|  |  | +        else
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var header = column.AddItem("Relocate Items", null, null);
 | 
	
		
			
				|  |  | +            column.AddItem("All Items", null, r => RelocateItems(holding, requiitems.ToArray()), header);
 | 
	
		
			
				|  |  | +            column.AddSeparator(header);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if (!holding.Available.EqualsWithTolerance(0.0F))
 | 
	
		
			
				|  |  | +                column.AddItem("Un-Requisitioned Items", null, r => RelocateItems(holding, requiitems.Where(x=>x.ID == Guid.Empty).ToArray()), header);
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            foreach (var requiitem in requiitems)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                string name =
 | 
	
		
			
				|  |  | +                    $"{requiitem.Job.JobNumber}:{requiitem.Requisition.Number} {requiitem.Requisition.Description} ({requiitem.Qty})";
 | 
	
		
			
				|  |  | +                column.AddItem(name, null, r => RelocateItems(holding, requiitems.Where(x=>x.ID == requiitem.ID).ToArray()), header);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private class StockLocationSelection : BaseObject
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        [EditorSequence(1)]
 | 
	
		
			
				|  |  | +        public StockLocationLink Location { get; set; }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        [EditorSequence(2)]
 | 
	
		
			
				|  |  | +        public double Qty { get; set; }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private class StockJobSelection : BaseObject
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        [EditorSequence(1)]
 | 
	
		
			
				|  |  | +        public JobLink Job { get; set; }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        [EditorSequence(2)]
 | 
	
		
			
				|  |  | +        public double Qty { get; set; }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    private void RelocateItems(StockHolding holding, JobRequisitionItem[] requiitems)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        var max = requiitems.Aggregate(0.0d, (t, x) => t += x.Qty);
 | 
	
		
			
				|  |  | +        var grid = new DynamicItemsListGrid<StockLocationSelection>();
 | 
	
		
			
				|  |  | +        grid.OnValidate += (sender, items, errors) =>
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            var total = items.Aggregate(0.0d, (t, x) => t += x.Qty);
 | 
	
		
			
				|  |  | +            if (total > max)
 | 
	
		
			
				|  |  | +                errors.Add($"Qty must not exceed {max}");
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        grid.OnCustomiseEditor += (sender, items, column, editor) =>
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (column.ColumnName.Equals(nameof(StockLocationSelection.Qty)) && requiitems?.Length != 1)
 | 
	
		
			
				|  |  | +                editor.Editable = Editable.Disabled;
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        var selection = new StockLocationSelection() { Qty = max };
 | 
	
		
			
				|  |  | +        if (grid.EditItems(new StockLocationSelection[] { selection }))
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            List<StockMovement> updates = new List<StockMovement>();
 | 
	
		
			
				|  |  | +            foreach (var requiitem in requiitems)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var mout = new StockMovement();
 | 
	
		
			
				|  |  | +                mout.Location.ID = holding.Location.ID;
 | 
	
		
			
				|  |  | +                mout.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | +                mout.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | +                mout.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | +                mout.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | +                mout.Issued = Math.Min(requiitem.Qty, selection.Qty);
 | 
	
		
			
				|  |  | +                mout.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | +                mout.JobRequisitionItem.ID = requiitem.ID;
 | 
	
		
			
				|  |  | +                mout.Transaction = Guid.NewGuid();
 | 
	
		
			
				|  |  | +                mout.Type = StockMovementType.TransferOut;
 | 
	
		
			
				|  |  | +                mout.Date = DateTime.Now;
 | 
	
		
			
				|  |  | +                mout.IsTransfer = true;
 | 
	
		
			
				|  |  | +                mout.Employee.ID = App.EmployeeID;
 | 
	
		
			
				|  |  | +                mout.Notes = $"Moved to {selection.Location.Code} by {App.EmployeeName}";
 | 
	
		
			
				|  |  | +                updates.Add(mout);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                var min = new StockMovement();
 | 
	
		
			
				|  |  | +                min.Location.ID = selection.Location.ID;
 | 
	
		
			
				|  |  | +                min.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | +                min.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | +                min.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | +                min.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | +                min.Received = mout.Issued;
 | 
	
		
			
				|  |  | +                min.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | +                min.JobRequisitionItem.ID = requiitem.ID;
 | 
	
		
			
				|  |  | +                min.Transaction = mout.Transaction;
 | 
	
		
			
				|  |  | +                min.Type = StockMovementType.TransferIn;
 | 
	
		
			
				|  |  | +                min.Date = mout.Date;
 | 
	
		
			
				|  |  | +                min.IsTransfer = true;
 | 
	
		
			
				|  |  | +                min.Employee.ID = App.EmployeeID;
 | 
	
		
			
				|  |  | +                min.Notes = $"Moved From {holding.Location.Code} by {App.EmployeeName}";
 | 
	
		
			
				|  |  | +                updates.Add(min);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            new Client<StockMovement>().Save(updates, "Relocated from Stock Locations Screen");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private void ViewRequisitions_Click(CoreRow? row)
 | 
	
	
		
			
				|  | @@ -165,16 +436,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          return smg;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    private Guid GetEmployeeID()
 | 
	
		
			
				|  |  | -    {
 | 
	
		
			
				|  |  | -        var employee = new Client<Employee>().Query(
 | 
	
		
			
				|  |  | -            new Filter<Employee>(x => x.UserLink.ID).IsEqualTo(ClientFactory.UserGuid),
 | 
	
		
			
				|  |  | -            new Columns<Employee>(x => x.ID)
 | 
	
		
			
				|  |  | -        );
 | 
	
		
			
				|  |  | -        return employee.Rows.Any() ? employee.Rows.First().Get<Employee, Guid>(x => x.ID) : Guid.Empty;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      private Dictionary<string, object?> StockMovementValueChanged(IDynamicEditorForm form, string name, object value)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          var result = new Dictionary<string, object?>();
 | 
	
	
		
			
				|  | @@ -192,8 +454,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          return result;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      private void StockMovementValidate(object sender, StockMovement[] items, List<string> errors)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          if (items.Any(x => x.Received == 0 && x.Issued == 0))
 | 
	
	
		
			
				|  | @@ -241,39 +502,33 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          if (column.ColumnName.Equals("Location.ID"))
 | 
	
		
			
				|  |  |              editor.Editable = _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (column.ColumnName.Equals("Product.ID"))
 | 
	
		
			
				|  |  | +            editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Disabled;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          if (column.ColumnName.Equals("Product.NettCost"))
 | 
	
		
			
				|  |  |              editor.Editable = Editable.Hidden;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          if (column.ColumnName.Equals("Style.ID"))
 | 
	
		
			
				|  |  |              editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        if (column.ColumnName.Equals("Received"))
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | -            editor.Caption = "Quantity";
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (column.ColumnName.Equals("Product.ID"))
 | 
	
		
			
				|  |  | -            editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          if (column.ColumnName.Equals("UnitSize"))
 | 
	
		
			
				|  |  |              editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          if (column.ColumnName.Equals("Job.ID"))
 | 
	
		
			
				|  |  |              editor.Editable = _action == MovementAction.Receive && items?.FirstOrDefault()?.Job.IsValid() == true ? Editable.Disabled : Editable.Enabled;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (column.ColumnName.Equals("Issued"))
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (column.ColumnName.Equals(nameof(StockMovement.Received)))
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            editor.Editable = _action == MovementAction.Issue ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | +            editor.Editable = _action == MovementAction.Receive  ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  |              editor.Caption = "Quantity";
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (column.ColumnName.Equals("Employee.ID"))
 | 
	
		
			
				|  |  | -            editor.Editable = _action == MovementAction.Transfer ? Editable.Hidden : Editable.Enabled;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        if (column.ColumnName.Equals("Units"))
 | 
	
		
			
				|  |  | -            editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | +        if (column.ColumnName.Equals(nameof(StockMovement.Issued)))
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            editor.Editable = _action == MovementAction.Issue || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
 | 
	
		
			
				|  |  | +            editor.Caption = "Quantity";
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private bool ReceiveStock(Button arg1, CoreRow[] rows)
 | 
	
	
		
			
				|  | @@ -287,7 +542,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |          movement.Location.Description = Location.Description;
 | 
	
		
			
				|  |  |          movement.Date = DateTime.Now;
 | 
	
		
			
				|  |  |          movement.IsTransfer = false;
 | 
	
		
			
				|  |  | -        movement.Employee.ID = _employeeid;
 | 
	
		
			
				|  |  | +        movement.Employee.ID = App.EmployeeID;
 | 
	
		
			
				|  |  |          movement.Type = StockMovementType.Receive;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          movement.CommitChanges();
 | 
	
	
		
			
				|  | @@ -310,7 +565,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |          var batch = new StockMovementBatch();
 | 
	
		
			
				|  |  |          batch.Type = type;
 | 
	
		
			
				|  |  |          batch.Notes = batch.Type + " batch created from Desktop Stock Location Screen";
 | 
	
		
			
				|  |  | -        batch.Employee.ID = _employeeid;
 | 
	
		
			
				|  |  | +        batch.Employee.ID = App.EmployeeID;
 | 
	
		
			
				|  |  |          new Client<StockMovementBatch>().Save(batch, "created from Desktop Stock Location Screen");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          foreach (var mvt in movements)
 | 
	
	
		
			
				|  | @@ -323,140 +578,183 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private bool IssueStock(Button arg1, CoreRow[] rows)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  | -        if (!rows.Any())
 | 
	
		
			
				|  |  | +        if (rows?.Length != 1)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              MessageWindow.ShowMessage("Please select an item to issue", "No selected items");
 | 
	
		
			
				|  |  |              return false;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          var holding = rows.First().ToObject<StockHolding>();
 | 
	
		
			
				|  |  | +        SelectAllocation(holding, (h, items) => DoIssue(h, items));
 | 
	
		
			
				|  |  | +        return false;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        if(holding.Available <= 0)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            MessageWindow.ShowMessage("There is no available stock in this holding to issue.", "No available stock");
 | 
	
		
			
				|  |  | -            return false;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | +    private void DoIssue(StockHolding holding, JobRequisitionItem[] requiitems)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // var jobid = rows.First().Get<StockHolding, Guid>(x => x.Job.ID);
 | 
	
		
			
				|  |  | -        // var jobno = rows.First().Get<StockHolding, string>(x => x.Job.JobNumber);
 | 
	
		
			
				|  |  | -        // var productid = rows.First().Get<StockHolding, Guid>(x => x.Product.ID);
 | 
	
		
			
				|  |  | -        // var styleid = rows.First().Get<StockHolding, Guid>(x => x.Style.ID);
 | 
	
		
			
				|  |  | -        // var stylecode = rows.First().Get<StockHolding, string>(x => x.Style.Code);
 | 
	
		
			
				|  |  | -        // var size = rows.First().Get<StockHolding, double>(x => x.UnitSize);
 | 
	
		
			
				|  |  | +        var updates = new List<StockMovement>();
 | 
	
		
			
				|  |  | +        // At this point we either have
 | 
	
		
			
				|  |  | +        // 1. All Items (Multiple requiitems) -> no editing available
 | 
	
		
			
				|  |  | +        // 2. UnRequid Items (single requiitem, requiid = empty) - Full Editor
 | 
	
		
			
				|  |  | +        // 3. Requi'd Item (single requiitem, requiid != empty - Quantity only
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        var movement = new StockMovement
 | 
	
		
			
				|  |  | +        if (requiitems.Length > 1)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            Date = DateTime.Now
 | 
	
		
			
				|  |  | +            if (MessageWindow.ShowOKCancel(
 | 
	
		
			
				|  |  | +                    "This will issue everything from this holding!\nAre you sure you wish to continue?",
 | 
	
		
			
				|  |  | +                    "Confirm Issue", null) == true)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                foreach (var requiitem in requiitems)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    var movement = CreateMovementFromHolding(holding);
 | 
	
		
			
				|  |  | +                    movement.Type = StockMovementType.Issue;
 | 
	
		
			
				|  |  | +                    movement.JobRequisitionItem.ID = requiitem.ID;
 | 
	
		
			
				|  |  | +                    movement.Issued = requiitem.Qty;
 | 
	
		
			
				|  |  | +                    movement.Transaction = Guid.NewGuid();
 | 
	
		
			
				|  |  | +                    movement.Notes = $"Issued by {App.EmployeeName}";
 | 
	
		
			
				|  |  | +                    updates.Add(movement);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                SaveBatch(StockMovementBatchType.Issue, updates.ToArray());
 | 
	
		
			
				|  |  | +                DoChanged();
 | 
	
		
			
				|  |  | +                Refresh(false,true);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        var sjs = new StockJobSelection();
 | 
	
		
			
				|  |  | +        sjs.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | +        sjs.Qty = requiitems[0].Qty;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        var sjg = new DynamicItemsListGrid<StockJobSelection>();
 | 
	
		
			
				|  |  | +        sjg.OnValidate += (sender, items, errors) =>
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if (items[0].Qty > requiitems[0].Qty)
 | 
	
		
			
				|  |  | +                    errors.Add($"Qty must not exceed {requiitems[0].Qty}");
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  | -        movement.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | -        movement.Location.Code = Location.Code;
 | 
	
		
			
				|  |  | -        movement.Location.Description = Location.Description;
 | 
	
		
			
				|  |  | -        movement.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | -        movement.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | -        movement.Job.JobNumber = holding.Job.JobNumber;
 | 
	
		
			
				|  |  | -        movement.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | -        movement.Style.Code = holding.Style.Code;
 | 
	
		
			
				|  |  | -        movement.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | -        movement.IsTransfer = false;
 | 
	
		
			
				|  |  | -        movement.Employee.ID = _employeeid;
 | 
	
		
			
				|  |  | -        movement.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | -        movement.Type = StockMovementType.Issue;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        movement.CommitChanges();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        var smg = CheckStockMovementGrid(MovementAction.Issue, holding);
 | 
	
		
			
				|  |  | -        var result = smg.EditItems(new[] { movement });
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        var mvts = new List<StockMovement>
 | 
	
		
			
				|  |  | +        sjg.OnCustomiseEditor += (sender, items, column, editor) =>
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            movement
 | 
	
		
			
				|  |  | +            if (column.ColumnName.Equals(CoreUtils.GetFullPropertyName<StockJobSelection, Guid>(x => x.Job.ID, ".")))
 | 
	
		
			
				|  |  | +                editor.Editable = requiitems[0].ID == Guid.Empty
 | 
	
		
			
				|  |  | +                    ? Editable.Enabled
 | 
	
		
			
				|  |  | +                    : Editable.Disabled;
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        // Issuing to a different job than you received into?
 | 
	
		
			
				|  |  | -        if (result && holding.Job.ID != movement.Job.ID)
 | 
	
		
			
				|  |  | +        if (sjg.EditItems(new StockJobSelection[] { sjs }))
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            // Issue from Old Job (Hidden)
 | 
	
		
			
				|  |  | -            var issue = new StockMovement
 | 
	
		
			
				|  |  | +            var issue = CreateMovementFromHolding(holding);
 | 
	
		
			
				|  |  | +            issue.Job.ID = sjs.Job.ID;
 | 
	
		
			
				|  |  | +            issue.Type = StockMovementType.Issue;
 | 
	
		
			
				|  |  | +            issue.JobRequisitionItem.ID = requiitems[0].ID;
 | 
	
		
			
				|  |  | +            issue.Issued = sjs.Qty;
 | 
	
		
			
				|  |  | +            issue.Transaction = Guid.NewGuid();
 | 
	
		
			
				|  |  | +            issue.Notes = $"Issued by {App.EmployeeName}";
 | 
	
		
			
				|  |  | +            updates.Add(issue);
 | 
	
		
			
				|  |  | +            if (holding.Job.ID != issue.Job.ID)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                Date = movement.Date
 | 
	
		
			
				|  |  | -            };
 | 
	
		
			
				|  |  | -            issue.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | -            issue.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | -            issue.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | -            issue.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | -            issue.Issued = movement.Issued;
 | 
	
		
			
				|  |  | -            issue.Transaction = movement.Transaction;
 | 
	
		
			
				|  |  | -            issue.Employee.ID = movement.Employee.ID;
 | 
	
		
			
				|  |  | -            issue.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | -            issue.IsTransfer = true;
 | 
	
		
			
				|  |  | -            issue.Notes = string.Format("Transferred from {0}", holding.Job.ID.Equals(Guid.Empty) ? "General Stock" : "Job " + holding.Job.JobNumber);
 | 
	
		
			
				|  |  | -            issue.System = true;
 | 
	
		
			
				|  |  | -            issue.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | -            issue.Type = StockMovementType.TransferOut;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            // Receive to New Job (Hidden)
 | 
	
		
			
				|  |  | -            var receive = new StockMovement();
 | 
	
		
			
				|  |  | -            receive.Date = movement.Date;
 | 
	
		
			
				|  |  | -            receive.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | -            receive.Job.ID = movement.Job.ID;
 | 
	
		
			
				|  |  | -            receive.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | -            receive.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | -            receive.Received = movement.Issued;
 | 
	
		
			
				|  |  | -            receive.Transaction = movement.Transaction;
 | 
	
		
			
				|  |  | -            receive.Employee.ID = movement.Employee.ID;
 | 
	
		
			
				|  |  | -            receive.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | -            receive.IsTransfer = true;
 | 
	
		
			
				|  |  | -            receive.Notes = string.Format("Transferred to {0}",
 | 
	
		
			
				|  |  | -                !movement.Job.IsValid() ? "General Stock" : "Job " + movement.Job.JobNumber);
 | 
	
		
			
				|  |  | -            receive.System = true;
 | 
	
		
			
				|  |  | -            receive.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | -            receive.Type = StockMovementType.TransferIn;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            new Client<StockMovement>().Save(new[] { issue, receive }, "");
 | 
	
		
			
				|  |  | -            mvts.Add(issue);
 | 
	
		
			
				|  |  | -            mvts.Add(receive);
 | 
	
		
			
				|  |  | +                var xferout = CreateMovementFromHolding(holding);
 | 
	
		
			
				|  |  | +                xferout.Type = StockMovementType.TransferOut;
 | 
	
		
			
				|  |  | +                xferout.JobRequisitionItem.ID = requiitems[0].ID;
 | 
	
		
			
				|  |  | +                xferout.Issued = sjs.Qty;
 | 
	
		
			
				|  |  | +                xferout.Transaction = issue.Transaction;
 | 
	
		
			
				|  |  | +                xferout.IsTransfer = true;
 | 
	
		
			
				|  |  | +                xferout.Notes = $"Issued by {App.EmployeeName}";
 | 
	
		
			
				|  |  | +                updates.Add(xferout);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +                var xferin = CreateMovementFromHolding(holding);
 | 
	
		
			
				|  |  | +                xferin.Job.ID = sjs.Job.ID;
 | 
	
		
			
				|  |  | +                xferin.Type = StockMovementType.TransferOut;
 | 
	
		
			
				|  |  | +                xferin.JobRequisitionItem.ID = requiitems[0].ID;
 | 
	
		
			
				|  |  | +                xferin.Received = sjs.Qty;
 | 
	
		
			
				|  |  | +                xferin.Transaction = issue.Transaction;
 | 
	
		
			
				|  |  | +                xferin.IsTransfer = true;
 | 
	
		
			
				|  |  | +                xferin.Notes = $"Issued by {App.EmployeeName}";
 | 
	
		
			
				|  |  | +                updates.Add(xferin);
 | 
	
		
			
				|  |  | +                
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            SaveBatch(StockMovementBatchType.Issue, updates.ToArray());
 | 
	
		
			
				|  |  | +            DoChanged();
 | 
	
		
			
				|  |  | +            Refresh(false,true);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        if (result)
 | 
	
		
			
				|  |  | +    private void SelectAllocation(StockHolding holding, Action<StockHolding,JobRequisitionItem[]> action)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        var requiitems = new Client<JobRequisitionItem>()
 | 
	
		
			
				|  |  | +            .Query(
 | 
	
		
			
				|  |  | +                new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
 | 
	
		
			
				|  |  | +                new Columns<JobRequisitionItem>(x=>x.ID)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Job.JobNumber)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Requisition.Number)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Requisition.Description)
 | 
	
		
			
				|  |  | +                    .Add(x=>x.Qty)
 | 
	
		
			
				|  |  | +            ).ToObjects<JobRequisitionItem>()
 | 
	
		
			
				|  |  | +            .ToList();
 | 
	
		
			
				|  |  | +        if (!holding.Available.EqualsWithTolerance(0.0F))
 | 
	
		
			
				|  |  | +            requiitems.Insert(0, new JobRequisitionItem() { Qty = holding.Available });
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        if (requiitems.Count <= 1)
 | 
	
		
			
				|  |  | +            action(holding, requiitems.ToArray());
 | 
	
		
			
				|  |  | +        else
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            DoChanged();
 | 
	
		
			
				|  |  | -            SaveBatch(StockMovementBatchType.Issue, mvts.ToArray());
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            ContextMenu menu = new ContextMenu();
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            MenuItem all = new MenuItem();
 | 
	
		
			
				|  |  | +            all.Header =
 | 
	
		
			
				|  |  | +                $"All Items ({holding.Units})";
 | 
	
		
			
				|  |  | +            all.Click += (sender, args) => action(holding,requiitems.ToArray());
 | 
	
		
			
				|  |  | +            menu.Items.Add(all);
 | 
	
		
			
				|  |  | +            menu.Items.Add(new Separator());
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (!holding.Available.EqualsWithTolerance(0.0F))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                MenuItem item = new MenuItem();
 | 
	
		
			
				|  |  | +                item.Header =
 | 
	
		
			
				|  |  | +                    $"Un-Requisitioned Items ({holding.Available})";
 | 
	
		
			
				|  |  | +                item.Click += (sender, args) =>
 | 
	
		
			
				|  |  | +                    action(holding, requiitems.Where(x => x.ID == Guid.Empty).ToArray());
 | 
	
		
			
				|  |  | +                menu.Items.Add(item);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            foreach (var requiitem in requiitems)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                MenuItem item = new MenuItem();
 | 
	
		
			
				|  |  | +                item.Header =
 | 
	
		
			
				|  |  | +                    $"{requiitem.Job.JobNumber}:{requiitem.Requisition.Number} {requiitem.Requisition.Description} ({requiitem.Qty})";
 | 
	
		
			
				|  |  | +                item.Click += (sender, args) => action(holding, requiitems.Where(x=>x.ID == requiitem.ID).ToArray());
 | 
	
		
			
				|  |  | +                menu.Items.Add(item);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            menu.IsOpen = true;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        return result;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |      private bool TransferStock(Button arg1, CoreRow[] rows)
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  | +        if (rows?.Length != 1)
 | 
	
		
			
				|  |  | +            return false;
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |          var holding = rows.First().ToObject<StockHolding>();
 | 
	
		
			
				|  |  | +        SelectAllocation(holding, (h,items) => DoTransfer(h, items) );
 | 
	
		
			
				|  |  | +        return false;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        if(holding.Available <= 0)
 | 
	
		
			
				|  |  | +    private void DoTransfer(StockHolding holding, JobRequisitionItem[] requiitems)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        if (requiitems.Length > 1 || requiitems[0].Requisition.ID != Guid.Empty)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            MessageWindow.ShowMessage("There is no available stock in this holding to transfer.", "No available stock");
 | 
	
		
			
				|  |  | -            return false;
 | 
	
		
			
				|  |  | +            RelocateItems(holding, requiitems);
 | 
	
		
			
				|  |  | +            return;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        var movement = new StockMovement();
 | 
	
		
			
				|  |  | -        movement.Date = DateTime.Now;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        movement.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | -        movement.Location.Code = Location.Code;
 | 
	
		
			
				|  |  | -        movement.Location.Description = Location.Description;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        movement.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | -        movement.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | -        movement.Job.JobNumber = holding.Job.JobNumber;
 | 
	
		
			
				|  |  | -        movement.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | -        movement.Style.Code = holding.Style.Code;
 | 
	
		
			
				|  |  | -        movement.Employee.ID = _employeeid;
 | 
	
		
			
				|  |  | -        // Must happen before Received gets set so that OnPropertyChanged has stuff to work with and thus Qty is not zero.
 | 
	
		
			
				|  |  | -        movement.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        var movement = CreateMovementFromHolding(holding);
 | 
	
		
			
				|  |  | +        movement.JobRequisitionItem.ID = requiitems[0].ID;
 | 
	
		
			
				|  |  |          movement.Received = holding.Units;
 | 
	
		
			
				|  |  |          movement.IsTransfer = true;
 | 
	
		
			
				|  |  | -        movement.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  |          movement.Type = StockMovementType.TransferIn;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        movement.CommitChanges();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          var smg = CheckStockMovementGrid(MovementAction.Transfer, holding);
 | 
	
		
			
				|  |  |          var result = smg.EditItems(new[] { movement });
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -467,23 +765,12 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          if (result)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | -            var other = new StockMovement();
 | 
	
		
			
				|  |  | -            other.Date = movement.Date;
 | 
	
		
			
				|  |  | -            other.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | -            other.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | -            other.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            other.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | -            //other.Location.Code = Location.Code;
 | 
	
		
			
				|  |  | -            //other.Location.Description = Location.Description;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -            other.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | +            var other = CreateMovementFromHolding(holding);
 | 
	
		
			
				|  |  |              other.Issued = movement.Received;
 | 
	
		
			
				|  |  |              other.Transaction = movement.Transaction;
 | 
	
		
			
				|  |  | -            other.Employee.ID = movement.Employee.ID;
 | 
	
		
			
				|  |  | -            other.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  |              other.IsTransfer = true;
 | 
	
		
			
				|  |  |              other.Type = StockMovementType.TransferOut;
 | 
	
		
			
				|  |  | +            movement.JobRequisitionItem.ID = requiitems[0].ID;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              var changes = new List<string>();
 | 
	
		
			
				|  |  |              if (movement.Location.ID != other.Location.ID)
 | 
	
	
		
			
				|  | @@ -511,7 +798,26 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |              DoChanged();
 | 
	
		
			
				|  |  |              SaveBatch(StockMovementBatchType.Transfer, mvts.ToArray());
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        return result;
 | 
	
		
			
				|  |  | +        Refresh(true,false);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private StockMovement CreateMovementFromHolding(StockHolding holding)
 | 
	
		
			
				|  |  | +    {
 | 
	
		
			
				|  |  | +        var movement = new StockMovement();
 | 
	
		
			
				|  |  | +        movement.Date = DateTime.Now;
 | 
	
		
			
				|  |  | +        movement.Location.ID = Location.ID;
 | 
	
		
			
				|  |  | +        movement.Location.Code = Location.Code;
 | 
	
		
			
				|  |  | +        movement.Location.Description = Location.Description;
 | 
	
		
			
				|  |  | +        movement.Product.ID = holding.Product.ID;
 | 
	
		
			
				|  |  | +        movement.Job.ID = holding.Job.ID;
 | 
	
		
			
				|  |  | +        movement.Job.JobNumber = holding.Job.JobNumber;
 | 
	
		
			
				|  |  | +        movement.Style.ID = holding.Style.ID;
 | 
	
		
			
				|  |  | +        movement.Style.Code = holding.Style.Code;
 | 
	
		
			
				|  |  | +        movement.Employee.ID = App.EmployeeID;
 | 
	
		
			
				|  |  | +        movement.Dimensions.CopyFrom(holding.Dimensions);
 | 
	
		
			
				|  |  | +        movement.Cost = holding.AverageValue;
 | 
	
		
			
				|  |  | +        movement.CommitChanges();
 | 
	
		
			
				|  |  | +        return movement;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      protected override void Reload(Filters<StockHolding> criteria, Columns<StockHolding> columns, ref SortOrder<StockHolding>? sort, Action<CoreTable?, Exception?> action)
 | 
	
	
		
			
				|  | @@ -523,13 +829,5 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
 | 
	
		
			
				|  |  |              criteria.Add(new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(Location.ID));
 | 
	
		
			
				|  |  |          base.Reload(criteria, columns, ref sort, action);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    protected override bool FilterRecord(CoreRow row)
 | 
	
		
			
				|  |  | -    {
 | 
	
		
			
				|  |  | -        // Hackety Hackety Hack Hack stupid doubles not totalling zero when they're supposed to
 | 
	
		
			
				|  |  | -        var result = base.FilterRecord(row);
 | 
	
		
			
				|  |  | -        if (result)
 | 
	
		
			
				|  |  | -            result = Math.Abs(row.Get<StockHolding, double>(x => x.Qty)) >= 0.000001;
 | 
	
		
			
				|  |  | -        return result;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  |  }
 |