Pārlūkot izejas kodu

Improvements to FactoryFloor screen

Kenric Nugteren 2 nedēļas atpakaļ
vecāks
revīzija
f6dfa71f08

+ 27 - 3
prs.classes/Entities/Manufacturing/ManufacturingPacket/ManufacturingPacketComponent.cs

@@ -1,4 +1,5 @@
 using InABox.Core;
+using System;
 
 namespace Comal.Classes
 {
@@ -10,19 +11,23 @@ namespace Comal.Classes
         public ManufacturingPacketLink Packet { get; set; }
 
         [EntityRelationship(DeleteAction.SetNull)]
+        [EditorSequence(1)]
         public ProductLink Product { get; set; }
 
         [TextBoxEditor]
-        [EditorSequence(1)]
+        [EditorSequence(2)]
         public string Description { get; set; }
+
+        [DimensionsEditor(typeof(ProductDimensions))]
+        [EditorSequence(4)]
+        public override ProductDimensions Dimensions { get; set; }
         
+        [EditorSequence(5)]
         public int Quantity { get; set; }
         
         [NullEditor]
         public int Consumed { get; set; }
 
-        public override ProductDimensions Dimensions { get; set; }
-
         /// <summary>
         /// When a picking list is creating for a <see cref="ManufacturingPacketComponent"/>, we save a link to the requisition,
         /// so that we don't require the same component twice.
@@ -33,6 +38,25 @@ namespace Comal.Classes
         static ManufacturingPacketComponent()
         {
             LinkedProperties.Register<ManufacturingPacketComponent, ProductLink, string>(x => x.Product, x => x.Name, x => x.Description);
+
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, Guid>(x => x.Product.UnitOfMeasure, x => x.ID, x => x.Dimensions.Unit.ID);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, string>(x => x.Product.UnitOfMeasure, x => x.Code, x => x.Dimensions.Unit.Code);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, string>(x => x.Product.UnitOfMeasure, x => x.Description, x => x.Dimensions.Unit.Description);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, bool>(x => x.Product.UnitOfMeasure, x => x.HasLength, x => x.Dimensions.Unit.HasLength);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, bool>(x => x.Product.UnitOfMeasure, x => x.HasHeight, x => x.Dimensions.Unit.HasHeight);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, bool>(x => x.Product.UnitOfMeasure, x => x.HasQuantity, x => x.Dimensions.Unit.HasQuantity);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, bool>(x => x.Product.UnitOfMeasure, x => x.HasWeight, x => x.Dimensions.Unit.HasWeight);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, bool>(x => x.Product.UnitOfMeasure, x => x.HasWidth, x => x.Dimensions.Unit.HasWidth);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, string>(x => x.Product.UnitOfMeasure, x => x.Formula, x => x.Dimensions.Unit.Formula);
+            LinkedProperties.Register<ManufacturingPacketComponent, ProductDimensionUnitLink, string>(x => x.Product.UnitOfMeasure, x => x.Format, x => x.Dimensions.Unit.Format);
+            
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, double>(x => x.Product.DefaultInstance.Dimensions, x => x.Height, x => x.Dimensions.Height);
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, double>(x => x.Product.DefaultInstance.Dimensions, x => x.Length, x => x.Dimensions.Length);
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, double>(x => x.Product.DefaultInstance.Dimensions, x => x.Quantity, x => x.Dimensions.Quantity);
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, double>(x => x.Product.DefaultInstance.Dimensions, x => x.Weight, x => x.Dimensions.Weight);
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, double>(x => x.Product.DefaultInstance.Dimensions, x => x.Width, x => x.Dimensions.Width);
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, double>(x => x.Product.DefaultInstance.Dimensions, x => x.Value, x => x.Dimensions.Value);
+            LinkedProperties.Register<ManufacturingPacketComponent, StockDimensions, string>(x => x.Product.DefaultInstance.Dimensions, x => x.UnitSize, x => x.Dimensions.UnitSize);
         }
     }
 }

+ 1 - 1
prs.classes/Entities/Product/ProductDocument.cs

@@ -3,7 +3,7 @@
 namespace Comal.Classes
 {
     [UserTracking(typeof(Product))]
-    public class ProductDocument : EntityDocument<ProductLink>, IManyToMany<Product, Document>, ILicense<ProductManagementLicense>
+    public class ProductDocument : EntityDocument<ProductLink>, ILicense<ProductManagementLicense>
     {
     }
 }

+ 218 - 61
prs.desktop/Panels/Factory/FactoryPanel.xaml.cs

@@ -52,6 +52,7 @@ namespace PRSDesktop
         private readonly string OVERDUE_COLOR = "Salmon";
         private readonly string PRIORITY_COLOR = "Red";
         private readonly string QA_COLOR = "Silver";
+        private readonly string TRACKING_COLOR = "LightYellow";
         private readonly string SELECTED_COLOR = "Yellow";
         private readonly string SHARED_COLOR = "Lime";
         private readonly string TREATED_COLOR = "DarkOrchid";
@@ -246,20 +247,22 @@ namespace PRSDesktop
         {
             var dlg = new MultiSelectDialog<Shipment>(
                 null,
-                Columns.None<Shipment>().Add(x => x.ID, x => x.Code, x => x.Description, x => x.BarCode),
+                Columns.None<Shipment>()
+                    .Add(x => x.ID)
+                    .Add(x => x.Code)
+                    .Add(x => x.BarCode),
                 false
             );
             if (dlg.ShowDialog())
             {
-                var barcode = dlg.Data().Rows.First().Get<Shipment, string>(x => x.BarCode);
-                ProcessCode(_scanners.FirstOrDefault(), barcode);
+                SelectRack(dlg.Data().Rows.First().ToObject<Shipment>());
             }
         }
 
         private void DoCloseRack(PanelAction obj)
         {
-            if (!string.IsNullOrWhiteSpace(rackbarcode))
-                ProcessCode(_scanners.FirstOrDefault(), rackbarcode);
+            if (SidePanel == FactorySidePanel.Racks)
+                CloseRack();
         }
 
 
@@ -680,28 +683,12 @@ namespace PRSDesktop
                     {
                         if (code == rackbarcode)
                         {
-                            SidePanel = FactorySidePanel.None;
-                            
-                            RackContents.ItemsSource = null;
-                            rackid = Guid.Empty;
-                            rackbarcode = "";
+                            CloseRack();
                             scanner?.Actions.SoundBeeper(BeepPattern.ThreeLowShort);
                         }
                         else
                         {
-                            RackContents.ItemsSource = null;
-
-                            rackid = shipment.ID;
-                            rackbarcode = code;
-                            var deliveryItems = DeliveryItems.Where(x => x.ShipmentLink.ID == rackid).ToArray();
-                            rackcontents.Clear();
-                            rackcontents.AddRange(deliveryItems);
-
-                            RackContents.ItemsSource = rackcontents;
-                            RackName.Content = $"Rack {shipment.Code}";
-                            RackCount.Content = deliveryItems.Length.ToString();
-
-                            SidePanel = FactorySidePanel.Racks;
+                            SelectRack(shipment);
                             scanner?.Actions.SoundBeeper(BeepPattern.ThreeHighShort);
                         }
                     }
@@ -926,11 +913,16 @@ namespace PRSDesktop
 
                         if(CurrentSection is not null)
                         {
-                            productDocuments = Client.Query<ProductDocument>(
+                            productDocuments = Client.Query(
                                 Filter<ProductDocument>.Where(x => x.EntityLink.ID).InQuery(
                                         Filter<ManufacturingPacketComponent>.Where(x => x.Packet.ID).IsEqualTo(packet.ID),
                                         x => x.Product.ID)
-                                    .And(x => x.Type.ID).IsEqualTo(CurrentSection.ProductDocumentType.ID))
+                                    .And(x => x.Type.ID).IsEqualTo(CurrentSection.ProductDocumentType.ID),
+                                Columns.None<ProductDocument>()
+                                    .Add(x => x.ID)
+                                    .Add(x => x.Superceded)
+                                    .Add(x => x.DocumentLink.ID)
+                                    .Add(x => x.DocumentLink.FileName))
                                 .ToArray<ProductDocument>();
                             foreach (var document in productDocuments)
                             {
@@ -943,7 +935,7 @@ namespace PRSDesktop
                                         PdfViewerTab.Items.Add(new DynamicTabItem
                                         {
                                             Header = document.DocumentLink.FileName,
-                                            Tag = document.DocumentLink
+                                            Tag = document
                                         });
                                     }
                             }
@@ -985,20 +977,31 @@ namespace PRSDesktop
                 QAGrid.OnChanged += QAGridChanged;
                 tab.Content = QAGrid;
             }
-            else if(tab.Tag is PDFDocumentLink pdfDocument)
+            else if(tab.Tag is EntityLink<Document> pdfDocument)
             {
+                var document = _setoutDocuments.FirstOrDefault(x => x.DocumentLink.ID == pdfDocument.ID);
+
                 var pdfEditor = new PDFEditorControl();
                 pdfEditor.LineColor = _settings.LineColor;
                 pdfEditor.TextSize = _settings.FontSize;
                 //PDFEditor.PrintAllowed = Security.IsAllowed<CanPrintFactoryFloorDrawings>();
                 pdfEditor.SaveAllowed = Security.IsAllowed<CanSaveFactoryFloorDrawings>();
-                var document = _setoutDocuments.FirstOrDefault(x => x.DocumentLink.ID == pdfDocument.ID);
                 pdfEditor.Watermark = packet.WaterMark;
                 pdfEditor.Document = document;
 
                 pdfEditor.PDFSettingsChanged += PDFEditorSettingsChanged;
                 tab.Content = pdfEditor;
             }
+            else if(tab.Tag is ProductDocument productDocument)
+            {
+                var pdfEditor = new PDFEditorControl();
+
+                pdfEditor.SaveAllowed = false;
+                pdfEditor.EditAllowed = false;
+                pdfEditor.Document = productDocument;
+
+                tab.Content = pdfEditor;
+            }
         }
 
         private void TearOffButton_Click(object sender, RoutedEventArgs e)
@@ -1008,7 +1011,8 @@ namespace PRSDesktop
                 var doc = pdfEditor.Document;
                 var window = new ThemableWindow();
                 var editor = new PDFEditorControl();
-                editor.SaveAllowed = Security.IsAllowed<CanSaveFactoryFloorDrawings>();
+                editor.SaveAllowed = pdfEditor.SaveAllowed;
+                editor.EditAllowed = pdfEditor.EditAllowed;
                 editor.Document = doc;
                 window.Content = editor;
                 window.Show();
@@ -1024,12 +1028,41 @@ namespace PRSDesktop
 
         private void CardSelected(object sender, MouseButtonEventArgs e)
         {
-            CurrentKanban = (sender as FrameworkElement)?.Tag as ManufacturingKanban;
+            if ((sender as FrameworkElement)?.Tag is not ManufacturingKanban kanban) return;
 
-            UpdateSelectedKanban();
+            var stage = GetPacketStage(kanban.PacketID);
+            if(stage is null || (stage.Station != -1 && stage.Station != CurrentStation))
+            {
+                return;
+            }
+
+            if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control))
+            {
+                if (TrackingKanbans.Remove(kanban))
+                {
+                    kanban.IsTracking = false;
+                    kanban.SelectedColor = kanban.ColorKey;
+
+                    if(CurrentKanban == kanban)
+                    {
+                        CurrentKanban = TrackingKanbans.FirstOrDefault();
+                    }
+                    UpdateSelectedKanban();
+                }
+                else
+                {
+                    CurrentKanban = kanban;
+                    UpdateSelectedKanban(addToTracking: true);
+                }
+            }
+            else
+            {
+                CurrentKanban = kanban;
+                UpdateSelectedKanban();
+            }
         }
 
-        private void UpdateSelectedKanban(bool uncheckothers = true)
+        private void UpdateSelectedKanban(bool uncheckothers = true, bool addToTracking = false)
         {
             if (CurrentKanban != null)
             {
@@ -1043,10 +1076,19 @@ namespace PRSDesktop
 
                 if(!TrackingKanbans.Any(x => x.PacketID == CurrentKanban.PacketID))
                 {
-                    TrackingKanbans.Clear();
+                    if (!addToTracking)
+                    {
+                        foreach(var kanban in TrackingKanbans)
+                        {
+                            kanban.IsTracking = false;
+                            kanban.SelectedColor = kanban.ColorKey;
+                        }
+                        TrackingKanbans.Clear();
+                    }
                     if(stage is not null && (stage.Station == -1 || stage.Station == CurrentStation))
                     {
                         TrackingKanbans.Add(CurrentKanban);
+                        CurrentKanban.IsTracking = true;
                     }
                 }
 
@@ -1056,6 +1098,11 @@ namespace PRSDesktop
                     other.SelectedColor = other.ColorKey;
                 }
 
+                foreach(var kanban in TrackingKanbans)
+                {
+                    kanban.SelectedColor = TRACKING_COLOR;
+                }
+
                 CurrentKanban.IsSelected = true;
                 CurrentKanban.SelectedColor = SELECTED_COLOR;
             }
@@ -1203,20 +1250,19 @@ namespace PRSDesktop
             Progress.Show("");
             if (double.TryParse(((Button)sender).Tag.ToString(), out var percent))
             {
-                var selected = new List<ManufacturingPacket>();
-                var stages = new List<ManufacturingPacketStage>();
+                var stages = new List<Tuple<ManufacturingKanban, ManufacturingPacket, ManufacturingPacketStage>>();
                 foreach (var kanban in TrackingKanbans)
                 {
-                    selected.Add(KanbanToPacket(kanban));
                     var stage = GetPacketStage(kanban.PacketID);
                     if(stage is not null)
                     {
-                        stages.Add(stage);
+                        stages.Add(new(kanban, KanbanToPacket(kanban), stage));
                     }
                 }
 
                 var updates = new List<ManufacturingPacketStage>();
-                foreach (var stage in stages)
+                var doRefresh = false;
+                foreach (var (_, _, stage) in stages)
                 {
                     var bChanged = false;
                     if (stage.Started == DateTime.MinValue)
@@ -1234,6 +1280,7 @@ namespace PRSDesktop
                     if (stage.Station != -1 && stage.Station != CurrentStation)
                     {
                         stage.Station = CurrentStation;
+                        doRefresh = true;
                         bChanged = true;
                     }
 
@@ -1242,7 +1289,22 @@ namespace PRSDesktop
                 }
 
                 Client.Save(updates, "Progress Updated from Factory Floor");
-                DoRefresh(true);
+
+                if (doRefresh)
+                {
+                    DoRefresh(true);
+                }
+                else
+                {
+                    foreach(var (kanban, packet, stage) in stages)
+                    {
+                        LoadModel(kanban, packet, stage, kanban.Checked, kanban.IsTracking);
+                    }
+                    var src = Kanban.ItemsSource;
+                    Kanban.ItemsSource = null;
+                    Kanban.ItemsSource = src;
+                    LoadKanban();
+                }
             }
 
             Progress.Close();
@@ -1325,8 +1387,6 @@ namespace PRSDesktop
 
             if (!model.Checked)
             {
-                foreach (var kanban in Kanbans.Where(x => x.Checked).ToArray())
-                    kanban.Checked = false;
                 model.Checked = true;
             }
 
@@ -1353,6 +1413,15 @@ namespace PRSDesktop
                 menu.AddItem("Remove from Current Workload", null, checkedStages, SetPending_Click);
             }
 
+            if(checkedKanbans.Any(x => !TrackingKanbans.Contains(x)))
+            {
+                menu.AddItem("Track Time", null, checkedKanbans, TrackTime_Click);
+            }
+            if(checkedKanbans.Any(TrackingKanbans.Contains))
+            {
+                menu.AddItem("Stop Tracking Time", null, checkedKanbans, StopTrackTime_Click);
+            }
+
             menu.AddItem("Assign To / Edit Trolley(s)", null, checkedKanbans, SetTrolleyClick);
             if(checkedPackets.Any(x => !x.Trolleys.IsNullOrWhiteSpace()))
             {
@@ -1425,6 +1494,45 @@ namespace PRSDesktop
             e.Handled = true;
         }
 
+        private void ClearChecked()
+        {
+            foreach(var kanban in Kanbans.Where(x => x.Checked))
+            {
+                kanban.Checked = false;
+            }
+        }
+
+        private void TrackTime_Click(ManufacturingKanban[] kanbans)
+        {
+            foreach(var kanban in kanbans)
+            {
+                TrackingKanbans.Add(kanban);
+                kanban.IsTracking = true;
+                kanban.SelectedColor = kanban.IsSelected ? SELECTED_COLOR : TRACKING_COLOR;
+            }
+
+            ClearChecked();
+
+            var src = Kanban.ItemsSource;
+            Kanban.ItemsSource = null;
+            Kanban.ItemsSource = src;
+        }
+
+        private void StopTrackTime_Click(ManufacturingKanban[] kanbans)
+        {
+            foreach(var kanban in kanbans)
+            {
+                TrackingKanbans.Remove(kanban);
+                kanban.IsTracking = false;
+                kanban.SelectedColor = kanban.IsSelected ? SELECTED_COLOR : kanban.ColorKey;
+            }
+            var src = Kanban.ItemsSource;
+            Kanban.ItemsSource = null;
+            Kanban.ItemsSource = src;
+
+            ClearChecked();
+        }
+
         private void SetTrolleyClick(ManufacturingKanban[] kanbans)
         {
             var trolleyPrePopulate = new HashSet<string>();
@@ -1477,6 +1585,8 @@ namespace PRSDesktop
                         Kanban.ItemsSource = src;
                     }
                 }
+
+            ClearChecked();
         }
 
         private void ClearTrolleyClick(ManufacturingKanban[] kanbans)
@@ -1506,6 +1616,8 @@ namespace PRSDesktop
                     Kanban.ItemsSource = src;
                 }
             }
+
+            ClearChecked();
         }
 
         private void SetCurrent_Click(ManufacturingPacketStage[] stages)
@@ -1513,6 +1625,8 @@ namespace PRSDesktop
             AddPacketToCurrentWorkload(stages);
             CurrentKanban = Kanbans.FirstOrDefault(x => x.ID.Equals(stages.First().Parent.ID.ToString()));
             UpdateSelectedKanban(false);
+
+            ClearChecked();
         }
 
         private void AddPacketToCurrentWorkload(params ManufacturingPacketStage[] stages)
@@ -1540,6 +1654,8 @@ namespace PRSDesktop
                         }
                     }
                 }
+
+            ClearChecked();
         }
 
         private void SetPending_Click(ManufacturingPacketStage[] stages)
@@ -1573,6 +1689,8 @@ namespace PRSDesktop
                     CurrentKanban = Kanbans.FirstOrDefault(x => x.PacketID == selectedID);
                     UpdateSelectedKanban();
                 }
+
+            ClearChecked();
         }
 
         private void UpdateFlag(ManufacturingPacket[] packets, Expression<Func<ManufacturingPacket, bool>> property, bool value)
@@ -1590,6 +1708,8 @@ namespace PRSDesktop
                     Client.Save(updates, CoreUtils.GetFullPropertyName(property, ".") + " Flag " + (value ? "Set" : "Cleared"));
                     Refresh();
                 }
+
+            ClearChecked();
         }
 
         private void SetPriority_Click(ManufacturingPacket[] packets)
@@ -1617,6 +1737,8 @@ namespace PRSDesktop
                 Progress.ShowModal("Updating Issues", progress => { Client.Save(pkt, "Updated Issues"); });
                 Refresh();
             }
+
+            ClearChecked();
         }
 
         private void SetHold_Click(ManufacturingPacket[] packets)
@@ -1665,6 +1787,8 @@ namespace PRSDesktop
                     Client.Save(updates, "Set Packet as Shared");
                     Refresh();
                 }
+
+            ClearChecked();
         }
 
         private void ClearShared_Click(ManufacturingPacketStage[] stages)
@@ -1685,6 +1809,8 @@ namespace PRSDesktop
                     Client.Save(updates, "Cleared Shared flag from Packet");
                     Refresh();
                 }
+
+            ClearChecked();
         }
 
         private void MovePacketClick((int station, ManufacturingPacketStage[] stages) item)
@@ -1701,6 +1827,16 @@ namespace PRSDesktop
                 Client.Save(stages, $"Moved Packet to Station #{station}");
                 Refresh();
             }
+
+            ClearChecked();
+        }
+
+        private void RequestMaterials_Click(ManufacturingPacket[] packets)
+        {
+            ManufacturingPanelColumn.CreatePickingList(packets, true,
+                () => new GlobalConfiguration<ManufacturingPanelProperties>().Load());
+
+            ClearChecked();
         }
 
         #region ManufacturingPacket Stuff
@@ -1734,7 +1870,7 @@ namespace PRSDesktop
 
         #region Stuff to Do with Kanbans
 
-        public List<ManufacturingKanban> TrackingKanbans { get; set; }
+        public List<ManufacturingKanban> TrackingKanbans { get; set; } = new();
 
         public ObservableCollection<ManufacturingKanban> Kanbans { get; set; }
 
@@ -1759,15 +1895,6 @@ namespace PRSDesktop
 
         #endregion
 
-        #region Shipping Rack Stuff
-
-        private DeliveryItem[] DeliveryItems = [];
-        private readonly ObservableList<DeliveryItem> rackcontents = new();
-        private Guid rackid = Guid.Empty;
-        private string rackbarcode = "";
-
-        #endregion
-
         #region Refresh / Reload
 
         private void CreateKanban(ManufacturingPacket packet, ManufacturingPacketStage stage, bool isChecked, bool isTracking)
@@ -1842,8 +1969,8 @@ namespace PRSDesktop
                             : packet.EstimatedDate)
                         : QA_COLOR;
 
-            model.SelectedColor = packet.ID == CurrentKanban?.PacketID
-                ? SELECTED_COLOR
+            model.SelectedColor = packet.ID == CurrentKanban?.PacketID ? SELECTED_COLOR
+                : isTracking ? TRACKING_COLOR
                 : model.ColorKey;
             model.SharedColor = stage.Station.Equals(-1)
                 ? SHARED_COLOR
@@ -2006,8 +2133,7 @@ namespace PRSDesktop
                         stageQuery,
                         pktQuery,
                         new KeyedQueryDef<DeliveryItem>(
-                            Filter<DeliveryItem>.Where(x => x.DeliveredDate).IsEqualTo(DateTime.MinValue)
-                                .And(x => x.ManufacturingPacketLink).LinkValid(),
+                            Filter.All<DeliveryItem>(),
                             Columns.None<DeliveryItem>().Add(
                                 x => x.ID,
                                 x => x.Barcode,
@@ -2052,8 +2178,11 @@ namespace PRSDesktop
 
                 var sorted = Kanbans.OrderBy(x => x.Assignee).ThenByDescending(x => x.Tags.Length).ThenBy(x => x.DueDate).ThenBy(x => x.JobName);
 
-                if (CurrentKanban != null && !Kanbans.Contains(CurrentKanban))
-                    CurrentKanban = sorted.FirstOrDefault();
+                if (CurrentKanban != null)
+                {
+                    CurrentKanban = Kanbans.FirstOrDefault(x => x.PacketID == CurrentKanban.PacketID)
+                        ?? sorted.FirstOrDefault();
+                }
 
                 Kanban.ItemsSource = null;
                 Kanban.ItemsSource = sorted;
@@ -2079,7 +2208,6 @@ namespace PRSDesktop
 
         #endregion
 
-
         #region QAChecks
 
         private ManufacturingTemplateStage[]? TemplateStages;
@@ -2161,12 +2289,41 @@ namespace PRSDesktop
 
         #endregion
 
-        private void RequestMaterials_Click(ManufacturingPacket[] packets)
+        #region Rack
+
+        private DeliveryItem[] DeliveryItems = [];
+        private readonly ObservableList<DeliveryItem> rackcontents = new();
+        private Guid rackid = Guid.Empty;
+        private string rackbarcode = "";
+
+        private void CloseRack()
         {
-            ManufacturingPanelColumn.CreatePickingList(packets, true,
-                () => new GlobalConfiguration<ManufacturingPanelProperties>().Load());
+            SidePanel = FactorySidePanel.None;
+            
+            RackContents.ItemsSource = null;
+            rackid = Guid.Empty;
+            rackbarcode = "";
+        }
+
+        private void SelectRack(Shipment shipment)
+        {
+            RackContents.ItemsSource = null;
+
+            rackid = shipment.ID;
+            rackbarcode = shipment.BarCode;
+            var deliveryItems = DeliveryItems.Where(x => x.ShipmentLink.ID == rackid).ToArray();
+            rackcontents.Clear();
+            rackcontents.AddRange(deliveryItems);
+
+            RackContents.ItemsSource = rackcontents;
+            RackName.Content = $"Rack {shipment.Code}";
+            RackCount.Content = deliveryItems.Length.ToString();
+
+            SidePanel = FactorySidePanel.Racks;
         }
 
+        #endregion
+
         private void _selectPackArea_OnClick(object sender, RoutedEventArgs e)
         {
             var dlg = new MultiSelectDialog<StockArea>(

+ 91 - 2
prs.desktop/Panels/Manufacturing/ManufacturingKanban.cs

@@ -1,4 +1,6 @@
 using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using PropertyChanged;
@@ -7,14 +9,23 @@ using Syncfusion.UI.Xaml.Kanban;
 namespace PRSDesktop
 {
     [DoNotNotify]
-    public class ManufacturingKanban : KanbanModel
+    public class ManufacturingKanban : INotifyPropertyChanged
     {
         public int Quantity { get; set; }
         public string JobName { get; set; }
         public DateTime CreatedDate { get; set; }
         public DateTime DueDate { get; set; }
         public BitmapImage? Image { get; set; }
-        public bool Checked { get; set; }
+        private bool _checked;
+        public bool Checked
+        {
+            get => _checked;
+            set
+            {
+                _checked = value;
+                OnPropertyChanged();
+            }
+        }
         public Guid SetoutID { get; set; }
         public string Status { get; set; }
         public object SelectedColor { get; set; }
@@ -45,6 +56,77 @@ namespace PRSDesktop
             }
         }
 
+        private string _id;
+        public string ID
+        {
+            get => _id;
+            set
+            {
+                _id = value;
+                OnPropertyChanged();
+            }
+        }
+        private string _colorKey;
+        public string ColorKey
+        {
+            get => _colorKey;
+            set
+            {
+                _colorKey = value;
+                OnPropertyChanged();
+            }
+        }
+        private string _title;
+        public string Title
+        {
+            get => _title;
+            set
+            {
+                _title = value;
+                OnPropertyChanged();
+            }
+        }
+        private string _description;
+        public string Description
+        {
+            get => _description;
+            set
+            {
+                _description = value;
+                OnPropertyChanged();
+            }
+        }
+        private string[] _tags = [];
+        public string[] Tags 
+        {
+            get => _tags;
+            set
+            {
+                _tags = value;
+                OnPropertyChanged();
+            }
+        }
+        private object _category;
+        public object Category
+        {
+            get => _category;
+            set
+            {
+                _category = value;
+                OnPropertyChanged();
+            }
+        }
+        private string _assignee;
+        public string Assignee
+        {
+            get => _assignee;
+            set
+            {
+                _assignee = value;
+                OnPropertyChanged();
+            }
+        }
+
         #region Group Display Properties
         //Automatically displays/hides the group section of a displayed packet, and sorts out things like the corner radius
         public CornerRadius MainSectionRadius { get; set; }
@@ -80,6 +162,13 @@ namespace PRSDesktop
             LeftSectionRadius = new CornerRadius(5, 0, 0, 5);
             MainSectionRadius = new CornerRadius(0, 5, 5, 0);
         }
+
+        public event PropertyChangedEventHandler? PropertyChanged;
+
+        protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
     }
 }