Bläddra i källkod

Added Stock Management to Factory Floor Panel
Ribbon Button visual elements are now bound to their relevant PanelActions

frogsoftware 1 år sedan
förälder
incheckning
82d877e031

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

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq.Expressions;
+using InABox.Clients;
 using InABox.Core;
 using PRSClasses;
 
@@ -198,6 +199,26 @@ namespace Comal.Classes
             holding.AverageValue = units.IsEffectivelyEqual(0.0F) ? 0.0d : cost / units;
             holding.Weight = holding.Qty * holding.Dimensions.Weight;
         }
+        
+        public static IEnumerable<JobRequisitionItem> LoadRequisitionItems(this StockHolding holding)
+        {
+            var items = 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>();
+            if (!holding.Available.IsEffectivelyEqual(0.0F))
+            {
+                var requi = new JobRequisitionItem() { Qty = holding.Available };
+                requi.Requisition.Description = "Unallocated Items";
+                items = CoreUtils.One(requi).Concat(items);
+            }
+
+            return items;
+        }
     }
     
 }

+ 14 - 1
prs.classes/Settings/FactoryFloorLocalSettings.cs

@@ -4,6 +4,13 @@ using InABox.Core;
 
 namespace Comal.Classes
 {
+
+    public enum FactorySidePanel
+    {
+        None,
+        Racks,
+        Packs,
+    }
     public class FactoryFloorLocalSettings : ILocalConfigurationSettings
     {
         public FactoryFloorLocalSettings()
@@ -12,13 +19,19 @@ namespace Comal.Classes
             Station = -1;
             LineColor = "#FF0000";
             FontSize = 12;
+            AreaID = Guid.Empty;
+            SidePanel = FactorySidePanel.None;
         }
 
         public Guid Section { get; set; }
         public int Station { get; set; }
-
+        public Guid AreaID { get; set; }
+        public FactorySidePanel SidePanel { get; set; }
         public string LineColor { get; set; }
         public int FontSize { get; set; }
+        
+        public DateTime CurrentBatchDate { get; set; }
+        public Guid CurrentBatchID { get; set; }
     }
 
     [Obsolete("Replacing with FactoryFloorLocalSettings", false)]

+ 12 - 6
prs.desktop/MainWindow.xaml.cs

@@ -65,7 +65,10 @@ using PRSDesktop.Components.Spreadsheet;
 using InABox.Wpf.Reports;
 using Comal.Classes.SecurityDescriptors;
 using System.Threading;
+using System.Windows.Data;
+using System.Windows.Media.Imaging;
 using PRSDesktop.Panels;
+using Syncfusion.SfSkinManager;
 
 namespace PRSDesktop;
 
@@ -3149,7 +3152,7 @@ public partial class MainWindow : IPanelHostControl
             ReportsBar.Items.Add(button);
         }
     }
-
+    
     public void CreatePanelAction(PanelAction action)
     {
         if (CurrentTab is null)
@@ -3171,15 +3174,18 @@ public partial class MainWindow : IPanelHostControl
                 LargeIcon = action.Image.AsBitmapImage(),
                 MinWidth = 60
             };
-            if (action.Menu is not null)
-            {
-                button.ContextMenu = action.Menu;
-            }
+            button.Bind<PanelAction, String>(Fluent.Button.HeaderProperty, x=>x.Caption, null);
+            button.Bind<PanelAction, Bitmap>(Fluent.Button.LargeIconProperty, x => x.Image, new BitmapToBitmapImageConverter());
+            button.Bind<PanelAction, ContextMenu?>(Fluent.Button.ContextMenuProperty, x=>x.Menu);
+            button.DataContext = action;
+            // if (action.Menu is not null)
+            // {
+            //     button.ContextMenu = action.Menu;
+            // }
             button.Click += (o, e) =>
             {
                 action.Execute();
             };
-
             CurrentModules.Add(button);
             Actions.Items.Add(button);
         }

+ 6 - 6
prs.desktop/Panels/EquipmentPlanner/EquipmentPlanner.xaml.cs

@@ -241,32 +241,32 @@ namespace PRSDesktop
 
         #region AutoGenerate Columns / Styling
         
-        private class EquipmentResourcePlannerBackgroundConverter : UtilityConverter<EquipmentPlannerValue, Brush>
+        private class EquipmentResourcePlannerBackgroundConverter : AbstractConverter<EquipmentPlannerValue, Brush>
         {
             public override Brush Convert(EquipmentPlannerValue value) => value?.Background ?? new SolidColorBrush(Colors.LightYellow)  { Opacity = 0.8 };
         }
 
-        private class EquipmentResourcePlannerForegroundConverter : UtilityConverter<EquipmentPlannerValue, Brush>
+        private class EquipmentResourcePlannerForegroundConverter : AbstractConverter<EquipmentPlannerValue, Brush>
         {
             public override Brush Convert(EquipmentPlannerValue value) => value?.Foreground ?? new SolidColorBrush(Colors.DimGray)  { Opacity = 0.8 };
         }
 
-        private class EquipmentResourcePlannerFontStyleConverter : UtilityConverter<EquipmentPlannerValue, FontStyle>
+        private class EquipmentResourcePlannerFontStyleConverter : AbstractConverter<EquipmentPlannerValue, FontStyle>
         {
             public override FontStyle Convert(EquipmentPlannerValue value) => FontStyles.Normal;
         }
 
-        private class EquipmentResourcePlannerFontWeightConverter : UtilityConverter<EquipmentPlannerValue, FontWeight>
+        private class EquipmentResourcePlannerFontWeightConverter : AbstractConverter<EquipmentPlannerValue, FontWeight>
         {
             public override FontWeight Convert(EquipmentPlannerValue value) => FontWeights.Normal;
         }
 
-        private class EquipmentResourcePlannerContentConverter : UtilityConverter<EquipmentPlannerValue, String?>
+        private class EquipmentResourcePlannerContentConverter : AbstractConverter<EquipmentPlannerValue, String?>
         {
             public override String? Convert(EquipmentPlannerValue value) => value?.Text;
         }
 
-        private class DateFormatConverter : UtilityConverter<DateTime, String>
+        private class DateFormatConverter : AbstractConverter<DateTime, String>
         {
             public String Format { get; private set; }
             

+ 368 - 0
prs.desktop/Panels/Factory/FactoryPackGrid.cs

@@ -0,0 +1,368 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Media;
+using Comal.Classes;
+using InABox.Clients;
+using InABox.Configuration;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using Document = InABox.Core.Document;
+
+namespace PRSDesktop;
+
+public class FactoryPackGrid : DynamicDataGrid<StockHolding>
+{
+    public Guid AreaID { get; set; }
+    
+    public ManufacturingPacket? CurrentPacket { get; set; }
+
+    public String AreaDescription
+    {
+        get => _holding.HeaderText;
+        set
+        {
+            _holding.HeaderText = value;
+            Refresh(true,false);
+        }
+    }
+
+    private readonly Dictionary<Guid, byte[]> _images = new();
+
+    private readonly DynamicActionColumn _holding;
+
+    public FactoryPackGrid()
+    {
+        HiddenColumns.Add(x=>x.ID);
+        HiddenColumns.Add(x=>x.Product.ID);
+        HiddenColumns.Add(x=>x.Product.Code);
+        HiddenColumns.Add(x=>x.Product.Name);
+        HiddenColumns.Add(x=>x.Product.Image.ID);
+        HiddenColumns.Add(x => x.Dimensions.Unit.ID);
+        HiddenColumns.Add(x => x.Dimensions.Unit.Formula);
+        HiddenColumns.Add(x => x.Dimensions.Unit.Format);
+        HiddenColumns.Add(x=>x.Dimensions.Height);
+        HiddenColumns.Add(x=>x.Dimensions.Width);
+        HiddenColumns.Add(x=>x.Dimensions.Length);
+        HiddenColumns.Add(x=>x.Dimensions.Weight);
+        HiddenColumns.Add(x=>x.Dimensions.Quantity);
+        HiddenColumns.Add(x=>x.Dimensions.Value);
+        HiddenColumns.Add(x=>x.Dimensions.UnitSize);
+        HiddenColumns.Add(x=>x.Style.ID);
+        HiddenColumns.Add(x=>x.Style.Code);
+        HiddenColumns.Add(x=>x.Style.Description);
+        HiddenColumns.Add(x => x.Job.ID);
+        HiddenColumns.Add(x=>x.Job.JobNumber);
+        HiddenColumns.Add(x=>x.Job.Name);
+        HiddenColumns.Add(x=>x.Location.ID);
+        HiddenColumns.Add(x=>x.Location.Code);
+        HiddenColumns.Add(x=>x.Location.Description);
+        HiddenColumns.Add(x=>x.Units);
+        HiddenColumns.Add(x => x.Available);
+        
+        _holding = new DynamicTemplateColumn(PackTemplate) { Width = 0 };
+        ActionColumns.Add(_holding);
+    }
+    
+    public override void Reconfigure()
+    {
+        base.Reconfigure();
+        RowHeight = 85;
+        Options.Clear();
+    }
+    
+    protected override DynamicGridColumns LoadColumns()
+    {
+        var columns = new DynamicGridColumns();
+        return columns;
+    }
+
+    protected override void Reload(Filters<StockHolding> criteria, Columns<StockHolding> columns, ref SortOrder<StockHolding>? sort, Action<CoreTable?, Exception?> action)
+    {
+        Task<CoreTable> imagetask = Task.Run(() =>
+        {
+            return new Client<Document>().Query(
+                new Filter<Document>(x => x.ID)
+                    .InQuery(new Filter<StockHolding>(x => x.Location.Area.ID).IsEqualTo(AreaID),
+                        x => x.Product.Image.ID),
+                new Columns<Document>(x => x.ID).Add(x => x.Data)
+            );
+        });
+        
+        criteria.Add(new Filter<StockHolding>(x => x.Location.Area.ID).IsEqualTo(AreaID));
+        
+        base.Reload(criteria, columns, ref sort, (o,e) =>
+        {
+            if (o != null)
+            {
+                Task.WaitAll(imagetask);
+                _images.Clear();
+                imagetask.Result.LoadDictionary<Document, Guid, byte[]>(
+                    _images,
+                    x => x.ID,
+                    x => x.Data
+                );
+            }
+            action(o,e);
+        });
+    }
+
+    private void CreateStackPanel(Grid grid, int row, int column, FontWeight weight,
+        params Expression<Func<StockHolding, object>>[] bindings)
+    {
+        StackPanel result = new StackPanel()
+        {
+            Orientation = Orientation.Horizontal,
+            HorizontalAlignment = HorizontalAlignment.Center,
+        };
+        result.SetValue(Grid.RowProperty,row);
+        result.SetValue(Grid.ColumnProperty,column);
+        foreach (var binding in bindings)
+        {
+            Label label = new Label()
+            {
+                Margin = new Thickness(5,0,0,0),
+                VerticalContentAlignment = VerticalAlignment.Center,
+                FontWeight = weight
+            };
+            label.Bind(Label.ContentProperty,binding);
+            result.Children.Add(label);
+        }
+        grid.Children.Add(result);
+    }
+    
+    private FrameworkElement PackTemplate()
+    {
+        var grid = new Grid();
+        
+        grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
+        grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
+        grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) });
+        
+        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width= new GridLength(28, GridUnitType.Pixel)});
+        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width= new GridLength(1, GridUnitType.Pixel)});
+        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width= new GridLength(90, GridUnitType.Pixel)});
+        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width= new GridLength(1, GridUnitType.Star)});
+        grid.ColumnDefinitions.Add(new ColumnDefinition() { Width= new GridLength(70, GridUnitType.Pixel)});
+        
+        Label label = new Label()
+        {
+            HorizontalAlignment = HorizontalAlignment.Center, 
+            VerticalAlignment = VerticalAlignment.Center,
+            LayoutTransform = new RotateTransform(-90)
+        };
+        label.Bind<StockHolding,String>(Label.ContentProperty, x=>x.Location.Code);
+        label.SetValue(Grid.RowProperty,0);
+        label.SetValue(Grid.RowSpanProperty,3);
+        label.SetValue(Grid.ColumnProperty,0);
+        grid.Children.Add(label);
+
+        Border border = new Border()
+        {
+            BorderThickness = new Thickness(0.75),
+            BorderBrush = new SolidColorBrush(Colors.DarkGray)
+        };
+        border.SetValue(Grid.RowProperty,0);
+        border.SetValue(Grid.RowSpanProperty,3);
+        border.SetValue(Grid.ColumnProperty,1);
+        grid.Children.Add(border);
+        
+        Image image = new Image()
+        {
+            Stretch = Stretch.Uniform, 
+            Margin=new Thickness(5)
+        };
+        image.Bind<StockHolding,Guid>(Image.SourceProperty, x => x.Product.Image.ID,  new GuidToImageSourceConverter() { Images = _images});
+        image.SetValue(Grid.RowProperty,0);
+        image.SetValue(Grid.RowSpanProperty,3);
+        image.SetValue(Grid.ColumnProperty,2);
+        grid.Children.Add(image);
+        
+        CreateStackPanel(grid, 0, 3, FontWeights.Bold, x => x.Product.Code, x => x.Product.Name);
+        CreateStackPanel(grid, 1, 3, FontWeights.Normal, x => x.Dimensions.UnitSize, x => x.Style.Code, x=>x.Style.Description);
+        CreateStackPanel(grid, 2, 3, FontWeights.Normal, x => x.Job.JobNumber, x => x.Job.Name);
+        
+        StackPanel buttonContent = new StackPanel()
+        {
+            Orientation = Orientation.Vertical,
+            VerticalAlignment = VerticalAlignment.Center,
+            HorizontalAlignment = HorizontalAlignment.Center
+        };
+        Label qtyLbl = new Label()
+        {
+            FontSize = 16,
+            FontWeight = FontWeights.Bold,
+            HorizontalAlignment = HorizontalAlignment.Center
+        };
+        qtyLbl.Bind<StockHolding, double>(Label.ContentProperty, x => x.Units, null, BindingMode.Default, "{0:F2}");
+        buttonContent.Children.Add(qtyLbl);
+        
+        Label useLbl = new Label()
+        {
+            HorizontalAlignment = HorizontalAlignment.Center,
+            Content = "Use"
+        };
+        buttonContent.Children.Add(useLbl);
+
+        Button button = new Button()
+        {
+            Margin = new Thickness(5),
+            Content = buttonContent
+        };
+        button.Bind<StockHolding,Guid>(Image.SourceProperty, x => x.ID);
+        button.SetValue(Grid.RowProperty,0);
+        button.SetValue(Grid.RowSpanProperty,4);
+        button.SetValue(Grid.ColumnProperty,4);
+        button.Click += UseStock;
+        grid.Children.Add(button);
+        
+        return grid;
+    }
+    
+    private class FactoryFloorIssue : BaseObject
+    {
+        
+        public JobRequisitionItem[]? RequisitionItems { get; init; }
+        
+        [EditorSequence(1)]
+        [ComboLookupEditor(typeof(RequisitionIDGenerator))]
+        public Guid RequisitionID { get; set; }
+
+        private class RequisitionIDGenerator : LookupGenerator<FactoryFloorIssue>
+        {
+            public RequisitionIDGenerator(FactoryFloorIssue[] items) : base(items)
+            {
+            }
+
+            protected override void DoGenerateLookups()
+            {
+                var items = Items?.FirstOrDefault()?.RequisitionItems;
+                if (items == null)
+                    return;
+                foreach (var item in items)
+                {
+                    AddValue(item.ID,
+                        item.ID == Guid.Empty
+                            ? $"{item.Requisition.Description}"
+                            : $"{item.Requisition.Number}: {item.Requisition.Description}");
+                }
+            }
+        }
+
+        [EditorSequence(2)]
+        public double Qty { get; set; }
+    }
+    
+    private void UseStock(object sender, RoutedEventArgs e)
+    {
+        
+        if (CurrentPacket == null)
+        {
+            MessageBox.Show("Please select a Manufacturing Packet before proceeding");
+            return;
+        }
+        
+        var holdingrow = SelectedRows.FirstOrDefault();
+        if (holdingrow == null)
+        {
+            MessageBox.Show("Please select a Stock Holding before proceeding");
+            return;
+        }
+        
+        var holding = holdingrow.ToObject<StockHolding>();
+        var requiitems = holding.LoadRequisitionItems().ToArray();
+        var ffi = new FactoryFloorIssue()
+        {
+            RequisitionItems = requiitems,
+            Qty = 1,
+            RequisitionID = requiitems.FirstOrDefault()?.ID ?? Guid.Empty
+        };
+
+        var ffg = new DynamicItemsListGrid<FactoryFloorIssue>();
+        
+        ffg.OnValidate += (_, _, errors) =>
+        {
+            var id = ffi.RequisitionID;
+            var requi = ffi.RequisitionItems.FirstOrDefault(x => x.ID == id);
+            if (requi == null)
+                errors.Add("Please select an allocation!");
+            else if (ffi.Qty > requi.Qty)
+                errors.Add($"Qty must not exceed {requi.Qty}");
+        };
+        if (!ffg.EditItems([ffi]))
+            return;
+
+        var _settings = new LocalConfiguration<FactoryFloorLocalSettings>().Load();
+        if (_settings.CurrentBatchDate != DateTime.Today)
+        {
+            StockMovementBatch batch = new StockMovementBatch
+            {
+                Type = StockMovementBatchType.Issue,
+                TimeStamp = DateTime.Now,
+                Notes = "Manufacturing Consumption"
+            };
+            batch.Employee.ID = App.EmployeeID;
+            new Client<StockMovementBatch>().Save(batch,"Created on Factory Floor Screen");
+            _settings.CurrentBatchDate = DateTime.Today;
+            _settings.CurrentBatchID = batch.ID;
+            new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
+        }
+        
+        List<StockMovement> updates = new();
+        Guid transactionid = Guid.NewGuid();
+        
+        if (CurrentPacket.SetoutLink.JobLink.ID != holding.Job.ID)
+        {
+            
+            var transferout = holding.CreateMovement();
+            transferout.JobRequisitionItem.ID = ffi.RequisitionID;
+            transferout.Batch.ID = _settings.CurrentBatchID;
+            transferout.Type = StockMovementType.TransferOut;
+            transferout.Issued = ffi.Qty;
+            transferout.Transaction = transactionid;
+            transferout.System = true;
+            transferout.Date = DateTime.Now;
+            transferout.Employee.ID = App.EmployeeID;
+            updates.Add(transferout);
+            
+            var transferin = holding.CreateMovement();
+            transferin.JobRequisitionItem.ID = ffi.RequisitionID;
+            transferin.Batch.ID = _settings.CurrentBatchID;
+            transferin.Type = StockMovementType.TransferIn;
+            transferin.Received = ffi.Qty;
+            transferin.Job.ID = CurrentPacket.SetoutLink.JobLink.ID;
+            transferin.Job.Synchronise(CurrentPacket.SetoutLink.JobLink);
+            transferin.Transaction = transactionid;
+            transferin.Date = DateTime.Now;
+            transferin.System = true;
+            transferin.Employee.ID = App.EmployeeID;
+            updates.Add(transferin);
+            
+        }
+        
+        var issue = holding.CreateMovement();
+        issue.JobRequisitionItem.ID = ffi.RequisitionID;
+        issue.Batch.ID = _settings.CurrentBatchID;
+        issue.Type = StockMovementType.Issue;
+        issue.Issued = ffi.Qty;
+        issue.Job.ID = CurrentPacket.SetoutLink.JobLink.ID;
+        issue.Job.Synchronise(CurrentPacket.SetoutLink.JobLink);
+        issue.Transaction = transactionid;
+        issue.Date = DateTime.Now;
+        issue.Employee.ID = App.EmployeeID;
+        issue.Notes = "Issued to Manufacturing";
+        updates.Add(issue);
+        
+        new Client<StockMovement>().Save(updates,"Issued Stock to Factory Floor", (_,_) => { });
+        if (issue.JobRequisitionItem.ID == Guid.Empty)
+            UpdateRow<StockHolding, double>(holdingrow, x=>x.Available, holding.Available - ffi.Qty,false);
+        UpdateRow<StockHolding,double>(holdingrow, x => x.Units, holding.Units - ffi.Qty,true);
+
+    }
+}

+ 96 - 3
prs.desktop/Panels/Factory/FactoryPanel.xaml

@@ -5,7 +5,9 @@
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              mc:Ignorable="d"
              d:DesignHeight="800" d:DesignWidth="1200"
-             xmlns:themes="clr-namespace:InABox.WPF.Themes;assembly=InABox.Wpf">
+             xmlns:themes="clr-namespace:InABox.WPF.Themes;assembly=InABox.Wpf"
+             xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
+             xmlns:prsDesktop="clr-namespace:PRSDesktop">
     <UserControl.Resources>
         <Style x:Key="UnselectedButton" TargetType="Button">
             <Setter Property="Margin" Value="0,0,2,0"/>
@@ -373,8 +375,99 @@
                     Click="CompleteButton_Click" IsEnabled="False" />
         </Grid>
 
-        <DockPanel x:Name="RackPanel" Grid.Row="0" Grid.RowSpan="4" Grid.Column="2" Margin="4,0,0,0" Width="500"
-                   Visibility="Collapsed" Background="LightYellow">
+        <Grid 
+            x:Name="PackPanel" 
+            Grid.Row="0" 
+            Grid.RowSpan="4" 
+            Grid.Column="2" 
+            Margin="4,0,0,0"
+            Width="500"
+            Visibility="Visible"
+            Background="Transparent">
+            
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+            
+            <Grid.RowDefinitions>
+                <RowDefinition Height="30"/>
+                <RowDefinition Height="*"/>
+            </Grid.RowDefinitions>
+            
+            <Button
+                x:Name="_selectPackArea"
+                Grid.Row="0"
+                Grid.Column="0"
+                Padding="10,0"
+                Margin="0,0,5,0"
+                BorderThickness="0.75"
+                Content="Change Area"
+                Click="_selectPackArea_OnClick"/> 
+            
+
+                <syncfusion:SfTextBoxExt
+                    x:Name="_packSearch"
+                    Grid.Row="0"
+                    Grid.Column="1"
+                    BorderThickness="0.75"
+                    BorderBrush="Gray"
+                    Background="LightYellow"
+                    HorizontalContentAlignment="Left"
+                    VerticalContentAlignment="Center"
+                    TextChanged="_packSearch_OnTextChanged">
+                    <syncfusion:SfTextBoxExt.Watermark>
+                        <Label 
+                            Content="Search" 
+                            HorizontalAlignment="Left" 
+                            VerticalAlignment="Center" 
+                            Opacity="0.5"/>
+                    </syncfusion:SfTextBoxExt.Watermark>
+                </syncfusion:SfTextBoxExt>
+
+            <CheckBox
+                x:Name="_packLinked"
+                Grid.Row="0"
+                Grid.Column="2"
+                Margin="5,0,0,0"
+                VerticalContentAlignment="Center"
+                Content="Linked"
+                IsChecked="True"
+                Checked="_packLinked_OnChecked"
+                Unchecked="_packLinked_OnChecked"
+                /> 
+            
+            <Button
+                x:Name="_addPack"
+                Grid.Row="0"
+                Grid.Column="3"
+                Padding="10,0"
+                Margin="5,0,0,0"
+                BorderThickness="0.75"
+                Content="Add Location"
+                Click="_addPack_OnClick"/> 
+            
+            <prsDesktop:FactoryPackGrid
+                x:Name="_packGrid"
+                Grid.Row="1"
+                Grid.Column="0"
+                Grid.ColumnSpan="4"
+                Margin="0,4,0,0"
+                OnFilterRecord="_packGrid_OnOnFilterRecord"/>
+            
+        </Grid>
+        
+        <DockPanel 
+            x:Name="RackPanel" 
+            Grid.Row="0" 
+            Grid.RowSpan="4" 
+            Grid.Column="2" 
+            Margin="4,0,0,0" 
+            Width="500"
+            Visibility="Collapsed"
+            Background="LightYellow">
             <Border DockPanel.Dock="Top" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0" Background="LightYellow">
                 <DockPanel>
                     <Label DockPanel.Dock="Right" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"

+ 243 - 88
prs.desktop/Panels/Factory/FactoryPanel.xaml.cs

@@ -16,14 +16,12 @@ using InABox.Clients;
 using InABox.Configuration;
 using InABox.Core;
 using InABox.DynamicGrid;
-using InABox.Reports;
 using InABox.Core.Reports;
 using InABox.Wpf.Reports;
 using InABox.WPF;
 using Motorola.Snapi;
 using Motorola.Snapi.Constants.Enums;
 using Motorola.Snapi.EventArguments;
-using org.apache.commons.io.comparator;
 using BarcodeType = Comal.Classes.BarcodeType;
 using Color = System.Drawing.Color;
 using Image = System.Windows.Controls.Image;
@@ -35,37 +33,47 @@ namespace PRSDesktop
     /// <summary>
     ///     Interaction logic for FactoryPanel.xaml
     /// </summary>
-    public partial class FactoryPanel : UserControl, IPanel<ManufacturingPacket>
+    public partial class FactoryPanel : IPanel<ManufacturingPacket>
     {
-        private Button _currentButton;
 
         private readonly BitmapImage barcode = PRSDesktop.Resources.barcode.AsBitmapImage();
         private readonly BitmapImage disabled = PRSDesktop.Resources.disabled.AsBitmapImage();
-
-        //private Document document = null;
-        private SetoutDocument[] documents = { };
+        private readonly BitmapImage speechbubble = PRSDesktop.Resources.speechbubble.AsBitmapImage();
+        private readonly BitmapImage starred = PRSDesktop.Resources.report.AsBitmapImage(48, 48);
         private readonly BitmapImage grouped = PRSDesktop.Resources.grouped.AsBitmapImage();
-        private readonly string NEARLYDUE_COLOR = "Orange";
+        
+        private SetoutDocument[] documents = [];
+        private readonly List<IMotorolaBarcodeScanner> _scanners = [];
 
+        private readonly string NEARLYDUE_COLOR = "Orange";
         private readonly string NOTYETDUE_COLOR = "PaleGreen";
         private readonly string OVERDUE_COLOR = "Salmon";
-        private PDFEditorControl PDFEditor;
         private readonly string PRIORITY_COLOR = "Red";
         private readonly string QA_COLOR = "Silver";
-
-        private QAGrid QAGrid;
-
-        public List<IMotorolaBarcodeScanner> Scanners = new();
         private readonly string SELECTED_COLOR = "Yellow";
-
-        private FactoryFloorLocalSettings settings;
         private readonly string SHARED_COLOR = "Lime";
-        private readonly BitmapImage speechbubble = PRSDesktop.Resources.speechbubble.AsBitmapImage();
-        private readonly BitmapImage starred = PRSDesktop.Resources.report.AsBitmapImage(48, 48);
         private readonly string TREATED_COLOR = "DarkOrchid";
 
+        private Button? _currentButton;
+        private PDFEditorControl? PDFEditor;
+        private QAGrid? QAGrid;
+        
+        private readonly FactoryFloorLocalSettings _settings;
+
+        private readonly PanelAction _togglePackPanel;
+        
         public FactoryPanel()
         {
+            
+            _settings = new LocalConfiguration<FactoryFloorLocalSettings>().Load();
+            if (_settings.Section.Equals(CoreUtils.FullGuid) && _settings.Station.Equals(-1))
+            {
+                var user = new UserConfiguration<FactoryFloorSettings>().Load();
+                _settings.Section = user.Section;
+                _settings.Station = user.Station;
+                new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
+            }
+            
             InitializeComponent();
 
             var CanConfigure = Security.IsAllowed<CanConfigureFactoryFloor>();
@@ -76,51 +84,66 @@ namespace PRSDesktop
 
             Kanbans = new ObservableCollection<ManufacturingKanban>();
             RackContents.ItemsSource = rackcontents;
+
+            RackPanel.Visibility = _settings.SidePanel == FactorySidePanel.Racks
+                ? Visibility.Visible
+                : Visibility.Collapsed;
+            
+            PackPanel.Visibility = _settings.SidePanel == FactorySidePanel.Packs
+                ? Visibility.Visible
+                : Visibility.Collapsed;
+            
+
         }
 
         public bool IsReady { get; set; }
 
         public void Setup()
         {
-            settings = new LocalConfiguration<FactoryFloorLocalSettings>().Load();
-            if (settings.Section.Equals(CoreUtils.FullGuid) && settings.Station.Equals(-1))
-            {
-                var user = new UserConfiguration<FactoryFloorSettings>().Load();
-                settings.Section = user.Section;
-                settings.Station = user.Station;
-                settings.LineColor = ColorTranslator.ToHtml(Color.Red);
-                settings.FontSize = 16;
-                new LocalConfiguration<FactoryFloorLocalSettings>().Save(settings);
-            }
 
-            var setups = Client.QueryMultiple(
-                new KeyedQueryDef<ManufacturingFactory>(),
-                new KeyedQueryDef<ManufacturingSection>(
-                    new Filter<ManufacturingSection>(x => x.Hidden).IsEqualTo(false),
-                    null,
-                    new SortOrder<ManufacturingSection>(x => x.Factory.Sequence).ThenBy(x => x.Sequence)),
-                new KeyedQueryDef<ManufacturingTrolley>(),
-                new KeyedQueryDef<Employee>(
-                    LookupFactory.DefineFilter<Employee>(),
-                    new Columns<Employee>(x => x.ID, x => x.Name, x => x.UserLink.ID),
-                    new SortOrder<Employee>(x => x.Name)),
-                new KeyedQueryDef<Shipment>(
-                    LookupFactory.DefineFilter<Shipment>(),
-                    new Columns<Shipment>(x => x.ID, x => x.Code, x => x.Description, x => x.BarCode),
-                    null));
-
-            Factories = setups[nameof(ManufacturingFactory)].Rows.Select(x => x.ToObject<ManufacturingFactory>()).ToArray();
-            Sections = setups[nameof(ManufacturingSection)].Rows.Select(x => x.ToObject<ManufacturingSection>()).ToArray();
-            Trolleys = setups[nameof(ManufacturingTrolley)].Rows.Select(x => x.ToObject<ManufacturingTrolley>()).ToArray();
-
-            Employees = setups[nameof(Employee)].ToDictionary<Employee, Guid, string>(x => x.ID, x => x.Name);
-            var myRow = setups[nameof(Employee)].Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.UserLink.ID).Equals(ClientFactory.UserGuid));
-            myID = myRow != null ? myRow.Get<Employee, Guid>(c => c.ID) : Guid.Empty;
-            myName = myRow != null ? myRow.Get<Employee, string>(c => c.Name) : "(Unknown Employee)";
-
-            Shipments = setups[nameof(Shipment)];
-
-            CurrentSection = Sections.FirstOrDefault(x => x.ID.Equals(settings.Section))
+            var setups = new MultiQuery();
+            
+            setups.Add<ManufacturingFactory>();
+            
+            setups.Add(
+                new Filter<ManufacturingSection>(x => x.Hidden).IsEqualTo(false),
+                null,
+                new SortOrder<ManufacturingSection>(x => x.Factory.Sequence).ThenBy(x => x.Sequence)
+            );
+
+            setups.Add<ManufacturingTrolley>();
+            
+            setups.Add(
+                LookupFactory.DefineFilter<Employee>(),
+                new Columns<Employee>(x => x.ID, x => x.Name, x => x.UserLink.ID),
+                new SortOrder<Employee>(x => x.Name)
+            );
+            
+            setups.Add(
+                LookupFactory.DefineFilter<Shipment>(),
+                new Columns<Shipment>(x => x.ID, x => x.Code, x => x.Description, x => x.BarCode)
+                );
+            
+            if (_settings.AreaID != Guid.Empty)
+                setups.Add(
+                    new Filter<StockArea>(x=>x.ID).IsEqualTo(_settings.AreaID),
+                    new Columns<StockArea>(x=>x.ID).Add(x=>x.Code).Add(x=>x.Description)
+                );
+            
+            setups.Query();
+
+            Factories = setups.Get<ManufacturingFactory>().Rows.Select(x => x.ToObject<ManufacturingFactory>()).ToArray();
+            Sections = setups.Get<ManufacturingSection>().Rows.Select(x => x.ToObject<ManufacturingSection>()).ToArray();
+            Trolleys = setups.Get<ManufacturingTrolley>().Rows.Select(x => x.ToObject<ManufacturingTrolley>()).ToArray();
+
+            Employees = setups.Get<Employee>().ToDictionary<Employee, Guid, string>(x => x.ID, x => x.Name);
+            var myRow = setups.Get<Employee>().Rows.FirstOrDefault(r => r.Get<Employee, Guid>(c => c.UserLink.ID).Equals(ClientFactory.UserGuid));
+            myID = myRow?.Get<Employee, Guid>(c => c.ID) ?? Guid.Empty;
+            myName = myRow?.Get<Employee, string>(c => c.Name) ?? "(Unknown Employee)";
+
+            Shipments = setups.Get<Shipment>();
+
+            CurrentSection = Sections.FirstOrDefault(x => x.ID.Equals(_settings.Section))
                 ?? Sections.FirstOrDefault();
             var iSection = 0;
             var iCount = 0;
@@ -132,16 +155,16 @@ namespace PRSDesktop
                     iCount = section.Stations; //Sections[id].Item2;
                 }
 
-                Section.Items.Add(string.Format("{0}: {1}", section.Factory.Name, section.Name));
+                Section.Items.Add($"{section.Factory.Name}: {section.Name}");
             }
 
             Section.SelectedIndex = iSection;
 
             Station.Items.Clear();
-            for (var i = 1; i <= (CurrentSection != null ? CurrentSection.Stations : 0); i++)
-                Station.Items.Add(string.Format("Station #{0}", i));
+            for (var i = 1; i <= (CurrentSection?.Stations ?? 0); i++)
+                Station.Items.Add($"Station #{i}");
 
-            CurrentStation = settings.Station + 1;
+            CurrentStation = _settings.Station + 1;
 
             if (CurrentStation < 1)
                 CurrentStation = 1;
@@ -151,6 +174,32 @@ namespace PRSDesktop
 
             Station.SelectedIndex = CurrentStation - 1;
 
+            if (_settings.AreaID != Guid.Empty)
+            {
+                var area = setups.Get<StockArea>().ToObjects<StockArea>().FirstOrDefault();
+                if (area != null)
+                {
+                    _packGrid.AreaID = _settings.AreaID;
+                    _packGrid.AreaDescription = $"{area.Code}: {area.Description}";
+                    _addPack.IsEnabled = true;
+                }
+                else
+                {
+                    _packGrid.AreaID = CoreUtils.FullGuid;
+                    _packGrid.AreaDescription = "(No Area Selected)";
+                    _settings.AreaID = Guid.Empty;
+                    new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
+                    _addPack.IsEnabled = false;
+                }
+                
+            }
+            else
+            {
+                _packGrid.AreaID = CoreUtils.FullGuid;
+                _packGrid.AreaDescription = "(No Area Selected)";
+            }
+
+            _packGrid.Refresh(true,true);
 
             SetupScanner();
         }
@@ -180,6 +229,24 @@ namespace PRSDesktop
             host.CreatePanelAction(new PanelAction { Caption = "Select Rack", Image = PRSDesktop.Resources.forklift, OnExecute = DoSelectRack });
             host.CreatePanelAction(new PanelAction { Caption = "Close Rack", Image = PRSDesktop.Resources.forklift, OnExecute = DoCloseRack });
             host.CreatePanelAction(new PanelAction { Caption = "Scan Barcode", Image = PRSDesktop.Resources.product, OnExecute = DoScanBarcode });
+            
+            host.CreatePanelAction(new PanelAction { Caption = _settings.SidePanel == FactorySidePanel.Packs ? "Hide Stock" : "Show Stock", Image = PRSDesktop.Resources.trolley, OnExecute = TogglePackPanel});
+        }
+
+        private void TogglePackPanel(PanelAction pa)
+        {
+            RackPanel.Visibility = Visibility.Collapsed;
+            PackPanel.Visibility = _settings.SidePanel == FactorySidePanel.Packs
+                ? Visibility.Collapsed
+                : Visibility.Visible;
+            _settings.SidePanel = PackPanel.Visibility == Visibility.Visible
+                ? FactorySidePanel.Packs
+                : FactorySidePanel.None;
+            new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
+            pa.Caption = _settings.SidePanel == FactorySidePanel.Packs
+                ? "Hide Stock"
+                : "Show Stock";
+            _addPack.IsEnabled = _settings.AreaID != Guid.Empty;
         }
 
         public Dictionary<string, object[]> Selected()
@@ -241,7 +308,7 @@ namespace PRSDesktop
                     {
                         var stagerow = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(packet.ID)
                                                                        && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                           .Equals(settings.Section));
+                                                                           .Equals(_settings.Section));
                         if (stagerow != null)
                         {
                             var stage = stagerow.ToObject<ManufacturingPacketStage>();
@@ -291,7 +358,7 @@ namespace PRSDesktop
         {
             try
             {
-                foreach (var scanner in Scanners) scanner.Actions.ToggleLed(LedMode.GreenOff);
+                foreach (var scanner in _scanners) scanner.Actions.ToggleLed(LedMode.GreenOff);
                 BarcodeScannerManager.Instance.DataReceived -= Instance_DataReceived;
 
                 BarcodeScannerManager.Instance.Close();
@@ -304,7 +371,7 @@ namespace PRSDesktop
 
         private void SetupScanner()
         {
-            Scanners.Clear();
+            _scanners.Clear();
             BarcodeScannerManager.Instance.Open();
 
             BarcodeScannerManager.Instance.RegisterForEvents(EventType.Barcode, EventType.Pnp, EventType.Image, EventType.Other, EventType.Rmd);
@@ -312,7 +379,7 @@ namespace PRSDesktop
             BarcodeScannerManager.Instance.GetDevices();
             foreach (var scanner in BarcodeScannerManager.Instance.GetDevices())
             {
-                Scanners.Add(scanner);
+                _scanners.Add(scanner);
                 scanner.Actions.ToggleLed(LedMode.RedOn);
                 scanner.Actions.SoundBeeper(BeepPattern.FastWarble);
             }
@@ -322,7 +389,7 @@ namespace PRSDesktop
 
         private void Instance_DataReceived(object? sender, BarcodeScanEventArgs e)
         {
-            Dispatcher.Invoke(() => { ProcessCode(Scanners[(int)e.ScannerId], e.Data); });
+            Dispatcher.Invoke(() => { ProcessCode(_scanners[(int)e.ScannerId], e.Data); });
         }
 
         private void ProcessCode(IMotorolaBarcodeScanner scanner, string code)
@@ -358,7 +425,7 @@ namespace PRSDesktop
                             var stagerow = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(packet.ID)
                                                                            && r.Get<ManufacturingPacketStage, Guid>(
                                                                                    c => c.ManufacturingSectionLink.ID)
-                                                                               .Equals(settings.Section));
+                                                                               .Equals(_settings.Section));
                             pktrow.Set<ManufacturingPacket, string>(x => x.Trolleys, packet.Trolleys);
 
                             LoadModel(kanban, pktrow, stagerow, true);
@@ -383,6 +450,9 @@ namespace PRSDesktop
                         if (string.Equals(code, rackbarcode))
                         {
                             RackPanel.Visibility = Visibility.Collapsed;
+                            _settings.SidePanel = FactorySidePanel.None;
+                            new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
+                            
                             RackContents.ItemsSource = null;
                             rackid = Guid.Empty;
                             rackbarcode = "";
@@ -399,10 +469,13 @@ namespace PRSDesktop
                             rackcontents.AddRange(rows.Select(x => x.ToObject<DeliveryItem>()));
 
                             RackContents.ItemsSource = rackcontents;
-                            RackName.Content = string.Format("Rack {0}", shiprow.Get<Shipment, string>(c => c.Code));
+                            RackName.Content = $"Rack {shiprow.Get<Shipment, string>(c => c.Code)}";
                             RackCount.Content = rows.Length.ToString();
 
+                            PackPanel.Visibility = Visibility.Collapsed;
                             RackPanel.Visibility = Visibility.Visible;
+                            _settings.SidePanel = FactorySidePanel.Racks;
+                            new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
                             scanner?.Actions.SoundBeeper(BeepPattern.ThreeHighShort);
                         }
                     }
@@ -462,7 +535,7 @@ namespace PRSDesktop
 
                             var stage = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(id)
                                                                         && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                            .Equals(settings.Section))?.ToObject<ManufacturingPacketStage>();
+                                                                            .Equals(_settings.Section))?.ToObject<ManufacturingPacketStage>();
 
                             if (stage == null)
                                 scanner?.Actions.SoundBeeper(BeepPattern.FourHighShort);
@@ -509,7 +582,7 @@ namespace PRSDesktop
                 var row = Packets.Rows.FirstOrDefault(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).Equals(pktid));
                 var stagerow = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(pktid)
                                                                && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                   .Equals(settings.Section));
+                                                                   .Equals(_settings.Section));
                 var packet = row.ToObject<ManufacturingPacket>();
 
                 if (id.Value == Guid.Empty)
@@ -539,8 +612,8 @@ namespace PRSDesktop
                 else
                 {
                     PDFEditor = new PDFEditorControl();
-                    PDFEditor.LineColor = settings.LineColor;
-                    PDFEditor.TextSize = settings.FontSize;
+                    PDFEditor.LineColor = _settings.LineColor;
+                    PDFEditor.TextSize = _settings.FontSize;
                     //PDFEditor.PrintAllowed = Security.IsAllowed<CanPrintFactoryFloorDrawings>();
                     PDFEditor.SaveAllowed = Security.IsAllowed<CanSaveFactoryFloorDrawings>();
                     var document = documents.FirstOrDefault(x => x.DocumentLink.ID.Equals(id));
@@ -564,14 +637,14 @@ namespace PRSDesktop
             {
                 var id = dlg.IDs().FirstOrDefault();
                 var barcode = dlg.Data().Rows.FirstOrDefault(r => r.Get<Shipment, Guid>(c => c.ID).Equals(id))?.Get<Shipment, string>(x => x.BarCode);
-                ProcessCode(Scanners.FirstOrDefault(), barcode);
+                ProcessCode(_scanners.FirstOrDefault(), barcode);
             }
         }
 
         private void DoCloseRack(PanelAction obj)
         {
             if (!string.IsNullOrWhiteSpace(rackbarcode))
-                ProcessCode(Scanners.FirstOrDefault(), rackbarcode);
+                ProcessCode(_scanners.FirstOrDefault(), rackbarcode);
         }
 
 
@@ -588,7 +661,7 @@ namespace PRSDesktop
                 var id = dlg.IDs().FirstOrDefault();
                 var barcode = dlg.Data().Rows.FirstOrDefault(r => r.Get<DeliveryItem, Guid>(c => c.ID).Equals(id))
                     ?.Get<DeliveryItem, string>(x => x.Barcode);
-                ProcessCode(Scanners.FirstOrDefault(), barcode);
+                ProcessCode(_scanners.FirstOrDefault(), barcode);
             }
         }
 
@@ -880,9 +953,9 @@ namespace PRSDesktop
         {
             CurrentKanban = null;
             CurrentStation = station;
-            settings.Section = section;
-            settings.Station = CurrentStation - 1;
-            new LocalConfiguration<FactoryFloorLocalSettings>().Save(settings);
+            _settings.Section = section;
+            _settings.Station = CurrentStation - 1;
+            new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
             DoRefresh(true);
         }
 
@@ -894,15 +967,17 @@ namespace PRSDesktop
                 {
                     ButtonStack.Children.Clear();
                     LoadDrawing(null);
+                    _packGrid.CurrentPacket = null;
                 }
                 else if (true) // Kanban has changed - how do we figure this out 
                 {
                     var packet = KanbanToPacket(CurrentKanban);
-
+                    _packGrid.CurrentPacket = packet;
+                    
                     //SetoutStage stage = packet.GetCurrentStage();
                     var row = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(packet.ID)
                                                               && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                  .Equals(settings.Section));
+                                                                  .Equals(_settings.Section));
                     var station = row != null ? row.Get<ManufacturingPacketStage, int>(c => c.Station) : 0;
                     var quality = row != null ? row.Get<ManufacturingPacketStage, QualityStatus>(c => c.QualityStatus) : QualityStatus.NotChecked;
                     var percentage = row != null ? row.Get<ManufacturingPacketStage, double>(c => c.PercentageComplete) : 0.00F;
@@ -989,6 +1064,8 @@ namespace PRSDesktop
                     else
                         LoadDrawing(null);
                 } // Kanban has not changed - do nothing
+                if (_packLinked.IsChecked == true)
+                    _packGrid.Refresh(false,false);
             }
         }
 
@@ -1107,7 +1184,7 @@ namespace PRSDesktop
                 var qacomplete = true;
                 var stage = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(packet.ID)
                                                             && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                .Equals(settings.Section));
+                                                                .Equals(_settings.Section));
                 if (stage != null)
                 {
                     var qadata = stage.Get<ManufacturingPacketStage, string>(c => c.FormData);
@@ -1464,7 +1541,7 @@ namespace PRSDesktop
             var checkedpackets = Packets.Rows.Where(r => checkedkanbans.Contains(r.Get<ManufacturingPacket, Guid>(c => c.ID)));
             var checkedstages = Stages.Rows.Where(r => checkedkanbans.Contains(r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID))
                                                        && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                           .Equals(settings.Section));
+                                                           .Equals(_settings.Section));
 
 
             // Only if we right-click on a checked packet??
@@ -1592,7 +1669,7 @@ namespace PRSDesktop
                         var pktrow = Packets.Rows.FirstOrDefault(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).Equals(update.ID));
                         var stagerow = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(update.ID)
                                                                        && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                           .Equals(settings.Section));
+                                                                           .Equals(_settings.Section));
 
                         //ManufacturingPacketStage stage = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(x => x.Parent.ID).Equals(update.ID))?.ToObject<ManufacturingPacketStage>();
                         var kanban = Kanbans.FirstOrDefault(x => x.ID.Equals(update.ID.ToString()));
@@ -1642,7 +1719,7 @@ namespace PRSDesktop
                         var pktrow = Packets.Rows.FirstOrDefault(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).Equals(Guid.Parse(kanban.ID)));
                         var stagerow = Stages.Rows.FirstOrDefault(r =>
                             r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(Guid.Parse(kanban.ID))
-                            && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID).Equals(settings.Section));
+                            && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID).Equals(_settings.Section));
 
                         var packet = pktrow.ToObject<ManufacturingPacket>();
 
@@ -1677,7 +1754,7 @@ namespace PRSDesktop
                     var stagerow = Stages.Rows.FirstOrDefault(r =>
                         r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(Guid.Parse(kanban.ID))
                         && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                            .Equals(settings.Section));
+                            .Equals(_settings.Section));
 
                     var packet = pktrow.ToObject<ManufacturingPacket>();
 
@@ -1936,9 +2013,9 @@ namespace PRSDesktop
 
         public void PDFEditorSettingsChanged(object sender, string linecolor, int fontsize)
         {
-            settings.LineColor = linecolor;
-            settings.FontSize = fontsize;
-            new LocalConfiguration<FactoryFloorLocalSettings>().Save(settings);
+            _settings.LineColor = linecolor;
+            _settings.FontSize = fontsize;
+            new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
         }
 
         #region Employee Stuff
@@ -2314,6 +2391,7 @@ namespace PRSDesktop
                         .Add(x => x.BarcodeQty)
                         .Add(x => x.SetoutLink.ID)
                         .Add(x => x.SetoutLink.Number)
+                        .Add(x => x.SetoutLink.JobLink.ID)
                         .Add(x => x.SetoutLink.JobLink.JobNumber)
                         .Add(x => x.SetoutLink.JobLink.Name)
                         .Add(x => x.SetoutLink.Group.ID)
@@ -2397,7 +2475,7 @@ namespace PRSDesktop
                     var id = pktrow.Get<ManufacturingPacket, Guid>(c => c.ID);
                     var distributed = pktrow.Get<ManufacturingPacket, bool>(c => c.Distributed);
 
-                    var stageIndex = stages.FindIndex(x => x.Item1 == id && x.Item2 == settings.Section);
+                    var stageIndex = stages.FindIndex(x => x.Item1 == id && x.Item2 == _settings.Section);
                     var stageRow = stageIndex >= 0 ? Stages.Rows[stageIndex] : null;
 
                     //this was commented out previously - leading to the distributed packet issue. Uncommented and tested to work by Nick. Was there a reason for commenting this out?
@@ -2492,7 +2570,7 @@ namespace PRSDesktop
                 //var row = Packets.Rows.FirstOrDefault(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).Equals(ck.ID));
                 var stagerow = Stages.Rows.FirstOrDefault(r => r.Get<ManufacturingPacketStage, Guid>(c => c.Parent.ID).Equals(Guid.Parse(ck.ID))
                                                                && r.Get<ManufacturingPacketStage, Guid>(c => c.ManufacturingSectionLink.ID)
-                                                                   .Equals(settings.Section));
+                                                                   .Equals(_settings.Section));
                 if (stagerow != null)
                 {
                     var stage = stagerow.ToObject<ManufacturingPacketStage>();
@@ -2577,5 +2655,82 @@ namespace PRSDesktop
             ManufacturingPanelColumn.CreatePickingList(rows!.Select(x => x.ToObject<ManufacturingPacket>()).ToList(), true,
                 () => new GlobalConfiguration<ManufacturingPanelProperties>().Load());
         }
+
+        private void _selectPackArea_OnClick(object sender, RoutedEventArgs e)
+        {
+            var dlg = new MultiSelectDialog<StockArea>(
+                new Filter<StockArea>(x => x.Active).IsEqualTo(true),
+                new Columns<StockArea>(x => x.ID).Add(x => x.Warehouse.Code).Add(x => x.Code).Add(x => x.Description),
+                false);
+            if (dlg.ShowDialog("Select Area"))
+            {
+                _settings.AreaID = dlg.IDs().FirstOrDefault();
+                new LocalConfiguration<FactoryFloorLocalSettings>().Save(_settings);
+                _packGrid.AreaDescription =
+                    dlg.Items(new Columns<StockArea>(x => x.Code).Add(x => x.Description))
+                        .Select(x => $"{x.Code}: {x.Description}").FirstOrDefault() ?? "(No Area Selected)";
+                _packGrid.AreaID = _settings.AreaID;
+                _packGrid.Refresh(false,true);
+            }
+        }
+
+        private void _addPack_OnClick(object sender, RoutedEventArgs e)
+        {
+            var dlg = new MultiSelectDialog<StockLocation>(
+                new Filter<StockLocation>(x => x.Area.ID).IsNotEqualTo(_settings.AreaID),
+                new Columns<StockLocation>(x => x.ID).Add(x => x.Warehouse.Code).Add(x => x.Area.Code).Add(x => x.Code).Add(x => x.Description),
+                false);
+            if (dlg.ShowDialog("Select Location"))
+            {
+                var location = dlg.Items().FirstOrDefault();
+                if (location != null)
+                {
+                    location.Area.ID = _settings.AreaID;
+                    new Client<StockLocation>().Save(location,"Moved To Manufacturing");
+                    _packGrid.Refresh(false,true);
+                }
+            }
+        }
+
+        private string _packFilter = "";
+
+        private void _packSearch_OnTextChanged(object sender, TextChangedEventArgs e)
+        {
+            _packGrid.Refresh(false, false);
+        }
+
+        private bool _packGrid_OnOnFilterRecord(CoreRow row)
+        {
+            var result = true;
+            
+            if (_packLinked.IsChecked == true)
+            {
+                Guid jobid = row.Get<StockHolding, Guid>(x => x.Job.ID);
+                if (_packGrid.CurrentPacket != null)
+                    result = jobid == Guid.Empty || jobid == _packGrid.CurrentPacket.SetoutLink.JobLink.ID;
+            }
+
+            result = result && (String.IsNullOrWhiteSpace(_packSearch.Text)
+                 || row.Get<StockHolding, String>(x => x.Product.Code).ToUpper().Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Product.Name).ToUpper().Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Location.Code).ToUpper().Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Style.Code).ToUpper().Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Style.Description).ToUpper()
+                     .Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Dimensions.UnitSize).ToUpper()
+                     .Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Job.JobNumber).ToUpper().Contains(_packSearch.Text.ToUpper())
+                 || row.Get<StockHolding, String>(x => x.Job.Name).ToUpper().Contains(_packSearch.Text.ToUpper()));
+            
+            return result;
+
+        }
+
+        private void _packLinked_OnChecked(object sender, RoutedEventArgs e)
+        {
+            _packGrid?.Refresh(false, false);
+        }
+        
+        
     }
 }

+ 4 - 17
prs.desktop/Panels/Products/Locations/StockHoldingGrid.cs

@@ -236,20 +236,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
             .EndUpdate();
     }
 
-    private IEnumerable<JobRequisitionItem> LoadRequisitionItems(StockHolding holding)
-    {
-        var items = Client.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>();
-        if (!holding.Available.IsEffectivelyEqual(0.0F))
-            items = CoreUtils.One(new JobRequisitionItem() { Qty = holding.Available }).Concat(items);
-        return items;
-    }
+
 
     private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
     {
@@ -263,7 +250,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
         
         column.AddSeparator();
 
-        var requiitems = LoadRequisitionItems(holding).ToList();
+        var requiitems = holding.LoadRequisitionItems().ToList();
         
         column.AddItem("Relocate Items", null, r => RelocateItems(holding, requiitems.ToArray()));
     }
@@ -409,7 +396,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
         }
         
         var holding = rows.First().ToObject<StockHolding>();
-        var items = LoadRequisitionItems(holding).AsArray();
+        var items = holding.LoadRequisitionItems().AsArray();
 
         DoIssue(holding, items);
         return false;
@@ -507,7 +494,7 @@ public class StockHoldingGrid : DynamicDataGrid<StockHolding>
             return false;
         
         var holding = rows.First().ToObject<StockHolding>();
-        var items = LoadRequisitionItems(holding).AsArray();
+        var items = holding.LoadRequisitionItems().AsArray();
 
         DoTransfer(holding, items);
         return false;

+ 1 - 1
prs.desktop/Panels/Tasks/KanbanResources.xaml.cs

@@ -103,7 +103,7 @@ public class ViewModeToKanbanTemplateConverter : IValueConverter
     }
 }
 
-public class KanbanStatusToStringConverter : UtilityConverter<KanbanStatus, string>
+public class KanbanStatusToStringConverter : AbstractConverter<KanbanStatus, string>
 {
     public override string Convert(KanbanStatus value)
     {

+ 1 - 1
prs.desktop/TestConverter.cs

@@ -4,7 +4,7 @@ using Syncfusion.UI.Xaml.Maps;
 
 namespace PRSDesktop;
 
-public class TestConverter : UtilityConverter<CustomDataSymbol,string>
+public class TestConverter : AbstractConverter<CustomDataSymbol,string>
 {
     public override string Convert(CustomDataSymbol value)
     {

+ 1 - 1
prs.desktop/prsdesktop.iss

@@ -5,7 +5,7 @@
 #pragma verboselevel 9
 
 #define MyAppName "PRS Desktop"
-#define MyAppVersion "7.77"
+#define MyAppVersion "7.78"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSDesktop.exe"

+ 1 - 1
prs.licensing/PRSLicensing.iss

@@ -5,7 +5,7 @@
 #pragma verboselevel 9
 
 #define MyAppName "PRS Licensing"
-#define MyAppVersion "7.77"
+#define MyAppVersion "7.78"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSLicensing.exe"

+ 1 - 1
prs.server/PRSServer.iss

@@ -5,7 +5,7 @@
 #pragma verboselevel 9
 
 #define MyAppName "PRS Server"
-#define MyAppVersion "7.77"
+#define MyAppVersion "7.78"
 #define MyAppPublisher "PRS Digital"
 #define MyAppURL "https://www.prs-software.com.au"
 #define MyAppExeName "PRSServer.exe"