Browse Source

New Manufacturing Status screen view

Kenric Nugteren 2 years ago
parent
commit
05592fe93c

+ 3 - 5
prs.classes/Entities/Manufacturing/ManufacturingPacket/ManufacturingPacket.cs

@@ -204,7 +204,7 @@ namespace Comal.Classes
             return string.Format("{0} {1}", SetoutLink.Number, Serial);
         }
 
-        public static void Progress(ManufacturingPacket[] packets, ManufacturingPacketStage[] Stages)
+        public static void Progress(IEnumerable<ManufacturingPacket> packets, ManufacturingPacketStage[] Stages)
         {
             //List<ManufacturingPacketStage> updates = new List<ManufacturingPacketStage>();
             foreach (var packet in packets.Where(x => !x.StageLink.Equals(CoreUtils.FullGuid)))
@@ -246,12 +246,10 @@ namespace Comal.Classes
             }
         }
 
-        public static void Regress(ManufacturingPacket[] pkts, ManufacturingPacketStage[] stgs)
+        public static void Regress(IEnumerable<ManufacturingPacket> pkts, ManufacturingPacketStage[] stgs)
         {
-            for (var i = 0; i < pkts.Length; i++)
+            foreach(var packet in pkts)
             {
-                //Progress.SetMessage(String.Format("Reverting: {0:F2}% complete", (double)i * 100.0F / (double)pkts.Length));
-                var packet = pkts[i];
                 var stages = stgs.Where(x => x.Parent.ID.Equals(packet.ID));
 
                 var sequence = long.MaxValue;

+ 8 - 3
prs.classes/Settings/ManufacturingSettings.cs

@@ -3,6 +3,13 @@ using InABox.Configuration;
 
 namespace Comal.Classes
 {
+    public enum ManufacturingViewType
+    {
+        Full,
+        Compact,
+        Job
+    }
+
     public class ManufacturingSettings : UserConfigurationSettings
     {
         [Obsolete("Repaced with FactoryID")]
@@ -12,9 +19,7 @@ namespace Comal.Classes
 
         public bool SortByDueDate { get; set; }
 
-        public bool CompactView { get; set; }
-
-        public bool ConsolidatedView { get; set; }
+        public ManufacturingViewType ViewType { get; set; }
 
         public bool IncludeHeld { get; set; }
 

+ 27 - 0
prs.desktop/Panels/Manufacturing/IManufacturingPanelColumn.cs

@@ -0,0 +1,27 @@
+using Comal.Classes;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSDesktop
+{
+    public interface IManufacturingPanelColumn
+    {
+        ManufacturingPanelData Data { set; }
+
+        string Title { get; set; }
+
+        bool Collapsed { get; set; }
+
+        Guid Category { get; set; }
+
+        event CollapsingEventHandler OnCollapsed;
+
+        void SetPackets(IEnumerable<ManufacturingPacket> packets);
+
+        IEnumerable<ManufacturingPacket> GetSelectedPackets();
+        IEnumerable<ManufacturingPacket> GetPackets();
+    }
+}

+ 1 - 0
prs.desktop/Panels/Manufacturing/ManufacturingPanel.xaml

@@ -68,6 +68,7 @@
                           SelectionChanged="View_SelectionChanged" VerticalContentAlignment="Center">
                     <ComboBoxItem Content="Full" />
                     <ComboBoxItem Content="Compact" />
+                    <ComboBoxItem Content="By Job" />
                     <!--<ComboBoxItem Content="Consolidated"/>-->
                 </ComboBox>
 

+ 123 - 405
prs.desktop/Panels/Manufacturing/ManufacturingPanel.xaml.cs

@@ -17,44 +17,38 @@ using Syncfusion.UI.Xaml.Kanban;
 
 namespace PRSDesktop
 {
+
     public partial class ManufacturingPanel : UserControl, IPanel<ManufacturingPacket>
     {
-        private readonly List<ManufacturingPanelColumn> _columns = new();
+        private readonly List<IManufacturingPanelColumn> _columns = new();
 
-        private readonly BitmapImage barcode = PRSDesktop.Resources.barcode.AsBitmapImage();
+        #region Settings Fields
 
         private bool bIncludeCompleted;
         private bool bIncludeHeld;
         private bool bIncludeOrders;
 
-        private readonly DispatcherTimer columnsizer = new();
-
-        private bool CompactView;
-        private bool ConsolidatedView;
+        private ManufacturingViewType ViewType;
         private Guid CurrentFactory = Guid.Empty;
-        public KanbanModel CurrentKanban = null;
-        private readonly BitmapImage disabled = PRSDesktop.Resources.disabled.AsBitmapImage();
 
-        private ManufacturingFactory[] Factories = { };
-        private Document[] FactoryImages = { };
+        private bool SortByDueDate = true;
 
-        private int iCombo;
-        private CoreTable ITPs;
+        private ManufacturingSettings settings = new();
+
+        #endregion
+
+        private readonly DispatcherTimer columnsizer = new();
 
+        private Document[] FactoryImages = Array.Empty<Document>();
 
+        private int iCombo;
         private bool JobChanging;
-        private CoreTable Jobs;
-        private readonly List<Tuple<Guid, DateTime, string>> OrderItems = new();
-        private CoreTable packets;
-        private ManufacturingSection[] Sections = { };
 
-        private ManufacturingSettings settings = new();
+        private readonly ManufacturingPanelData Data = new();
 
-        //ManufacturingTemplateStage[] TemplateStages = null; 
+        private CoreTable packets;
 
-        private bool SortByDueDate = true;
-        private readonly BitmapImage speechbubble = PRSDesktop.Resources.speechbubble.AsBitmapImage();
-        private ManufacturingTemplate[] Templates = { };
+        private Guid SelectedJob;
 
         public ManufacturingPanel()
         {
@@ -67,8 +61,6 @@ namespace PRSDesktop
 
         //public List<String> CheckedKanbans = new List<string>();
 
-        public ObservableCollection<ManufacturingKanban> Kanbans { get; set; }
-
         public bool IsReady { get; set; }
 
         public Dictionary<string, object[]> Selected()
@@ -112,14 +104,14 @@ namespace PRSDesktop
                     null
                 )
             );
-            Factories = tables[0].Rows.Select(x => x.ToObject<ManufacturingFactory>())
+            Data.Factories = tables[0].Rows.Select(x => x.ToObject<ManufacturingFactory>())
                 .ToArray(); //new Client<ManufacturingFactory>().Load(null, new SortOrder<ManufacturingFactory>(x=>x.Sequence));
-            Sections = tables[1].Rows.Select(x => x.ToObject<ManufacturingSection>())
+            Data.Sections = tables[1].Rows.Select(x => x.ToObject<ManufacturingSection>())
                 .ToArray(); //new Client<ManufacturingSection>().Load(null, new SortOrder<ManufacturingSection>(x=>x.Sequence));
-            Templates = tables[2].Rows.Select(x => x.ToObject<ManufacturingTemplate>())
+            Data.Templates = tables[2].Rows.Select(x => x.ToObject<ManufacturingTemplate>())
                 .ToArray(); //new Client<ManufacturingTemplate>().Load(null, new SortOrder<ManufacturingTemplate>(x=>x.Code));
-            Jobs = tables[3]; //.Rows.Select(x => x.ToObject<Job>()).ToArray();
-            ITPs = tables[4]; //.Rows.Select(x => x.ToObject<JobITP>()).ToArray();
+            Data.Jobs = tables[3]; //.Rows.Select(x => x.ToObject<Job>()).ToArray();
+            Data.ITPs = tables[4]; //.Rows.Select(x => x.ToObject<JobITP>()).ToArray();
             foreach (var orderrow in tables[5].Rows)
             {
                 var id = orderrow.Get<PurchaseOrderItem, Guid>(x => x.ID);
@@ -135,7 +127,7 @@ namespace PRSDesktop
 
                 var tag = receiveddate.IsEmpty() ? "ETA" : "RCVD";
 
-                OrderItems.Add(new Tuple<Guid, DateTime, string>(
+                Data.OrderItems.Add(new Tuple<Guid, DateTime, string>(
                     id,
                     receiveddate,
                     string.Format("{0} ({1}) {2} {3:dd MMM yy} {4}",
@@ -149,8 +141,8 @@ namespace PRSDesktop
             }
             //OrderItems = tables[5]; //.Rows.Select(x => x.ToObject<PurchaseOrderItem>()).ToArray(); 
 
-            Filter<Document> imageFilter = null;
-            foreach (var Factory in Factories)
+            Filter<Document>? imageFilter = null;
+            foreach (var Factory in Data.Factories)
                 if (Factory.Thumbnail.IsValid())
                     imageFilter = imageFilter == null
                         ? new Filter<Document>(x => x.ID).IsEqualTo(Factory.Thumbnail.ID)
@@ -158,13 +150,14 @@ namespace PRSDesktop
             FactoryImages = new Client<Document>().Load(imageFilter);
 
             var iFact = 1;
-            foreach (var Factory in Factories)
+            foreach (var Factory in Data.Factories)
             {
                 var groups = new List<string>();
                 if (!FactorySource.Any(x => x.Item1.Equals(Factory.Name)))
                 {
                     var image = FactoryImages.FirstOrDefault(x => x.ID.Equals(Factory.Thumbnail.ID));
-                    BitmapImage img = null;
+
+                    BitmapImage img;
                     if (image != null && image.Data != null && image.Data.Length > 0)
                     {
                         img = new BitmapImage();
@@ -186,7 +179,7 @@ namespace PRSDesktop
             FactoryListBox.SelectedIndex = iFact < FactorySource.Count ? iFact : 0;
 
             SortBy.SelectedIndex = settings.SortByDueDate ? 1 : 0;
-            View.SelectedIndex = settings.CompactView ? 1 : 0;
+            View.SelectedIndex = (int)ViewType;
 
             bIncludeHeld = settings.IncludeHeld;
             IncludeHeld.IsChecked = bIncludeHeld;
@@ -219,12 +212,13 @@ namespace PRSDesktop
             //    });
 
             var jobs = new Dictionary<Guid, string> { { CoreUtils.FullGuid, "All Jobs" } };
-            foreach (var row in Jobs.Rows)
+            foreach (var row in Data.Jobs.Rows)
                 jobs[row.Get<Job, Guid>(c => c.ID)] =
                     string.Format("{0}: {1}", row.Get<Job, string>(c => c.JobNumber), row.Get<Job, string>(c => c.Name));
             JobChanging = true;
             Job.ItemsSource = jobs;
             Job.SelectedValue = CoreUtils.FullGuid;
+            SelectedJob = CoreUtils.FullGuid;
             ITP.IsEnabled = false;
             JobChanging = false;
         }
@@ -245,12 +239,12 @@ namespace PRSDesktop
             foreach (var column in _columns)
             {
                 var rows = selection == Selection.None
-                    ? new CoreRow[] { }
+                    ? Enumerable.Empty<ManufacturingPacket>()
                     : selection == Selection.Selected
-                        ? column.GetSelectedRows(Guid.Empty.ToString())
-                        : column.packets.Rows;
+                        ? column.GetSelectedPackets()
+                        : column.GetPackets();
 
-                ids.AddRange(rows.Select(r => r.Get<ManufacturingPacket, Guid>(c => c.ID)));
+                ids.AddRange(rows.Select(r => r.ID));
             }
 
             return new ManufacturingPacketDataModel(new Filter<ManufacturingPacket>(x => x.ID).InList(ids.ToArray()));
@@ -274,7 +268,7 @@ namespace PRSDesktop
             Application.Current.Dispatcher.Invoke(() => { Mouse.OverrideCursor = null; });
         }
 
-        public event DataModelUpdateEvent OnUpdateDataModel;
+        public event DataModelUpdateEvent? OnUpdateDataModel;
 
         //private void ChangeDate_Click(object sender, RoutedEventArgs e)
         //{
@@ -398,10 +392,14 @@ namespace PRSDesktop
 
         private void Job_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
+            var oldID = SelectedJob;
+            var newID = Job.SelectedValue != null ? (Guid)Job.SelectedValue : Guid.Empty;
+            SelectedJob = newID;
+
             //Dictionary<Guid, String> Levels = new Dictionary<Guid, string>() { { CoreUtils.FullGuid, "All Levels" } };
             //Dictionary<Guid, String> Zones = new Dictionary<Guid, string>() { { CoreUtils.FullGuid, "All Zones" } };
             var itps = new Dictionary<Guid, string> { { CoreUtils.FullGuid, "All ITPs" } };
-            if (Job.SelectedValue == null || (Guid)Job.SelectedValue == CoreUtils.FullGuid)
+            if (SelectedJob == Guid.Empty || SelectedJob == CoreUtils.FullGuid)
             {
                 iCombo = 1;
                 //Level.ItemsSource = Levels;
@@ -417,81 +415,32 @@ namespace PRSDesktop
                 ITP.IsEnabled = false;
 
                 iCombo = 0;
-                if (!JobChanging)
-                    ReloadPackets();
-                return;
             }
+            else
+            {
+                iCombo = 1;
 
-            iCombo = 1;
-
-            foreach (var row in ITPs.Rows.Where(r => r.Get<JobITP, Guid>(c => c.Job.ID).Equals(Job.SelectedValue)))
-                itps[row.Get<JobITP, Guid>(c => c.Job.ID)] =
-                    string.Format("{0}: {1}", row.Get<JobITP, string>(c => c.Code), row.Get<JobITP, string>(c => c.Description));
-
-            ITP.ItemsSource = itps;
-            ITP.SelectedValue = CoreUtils.FullGuid;
-            ITP.IsEnabled = true;
-            iCombo--;
-            if (iCombo == 0)
-                ReloadPackets();
-
-            //new Client<JobLevel>().Query(
-            //    new Filter<JobLevel>(x=>x.Job.ID).IsEqualTo((Guid)Job.SelectedValue),
-            //    LookupFactory.DefineColumns<JobLevel>(),
-            //    LookupFactory.DefineSort<JobLevel>(),
-            //    (table, error) =>
-            //    {
-            //        foreach (var row in table.Rows)
-            //            Levels[row.Get<JobLevel, Guid>(x => x.ID)] = String.Format("{0}: {1}", row.Get<JobLevel,String>(x=>x.Code), row.Get<JobLevel,String>(x=>x.Description));
-            //        Dispatcher.Invoke(() =>
-            //        {
-            //            Level.ItemsSource = Levels;
-            //            Level.SelectedValue = CoreUtils.FullGuid;
-            //            Level.IsEnabled = true;
-
-            //            iCombo--;
-            //            if (iCombo == 0)
-            //                ReloadPackets();
-            //        });
-            //    });
+                foreach (var row in Data.ITPs.Rows.Where(r => r.Get<JobITP, Guid>(c => c.Job.ID).Equals(SelectedJob)))
+                    itps[row.Get<JobITP, Guid>(c => c.Job.ID)] =
+                        string.Format("{0}: {1}", row.Get<JobITP, string>(c => c.Code), row.Get<JobITP, string>(c => c.Description));
 
-            // new Client<JobZone>().Query(
-            //    new Filter<JobZone>(x => x.Job.ID).IsEqualTo((Guid)Job.SelectedValue),
-            //    LookupFactory.DefineColumns<JobZone>(),
-            //    LookupFactory.DefineSort<JobZone>(),
-            //    (table, error) =>
-            //    {
-            //        foreach (var row in table.Rows)
-            //            Zones[row.Get<JobZone, Guid>(x => x.ID)] = String.Format("{0}: {1}", row.Get<JobZone, String>(x => x.Code), row.Get<JobZone, String>(x => x.Description));
-            //        Dispatcher.Invoke(() =>
-            //        {
-            //            Zone.ItemsSource = Zones;
-            //            Zone.SelectedValue = CoreUtils.FullGuid;
-            //            Zone.IsEnabled = true;
-            //            iCombo--;
-            //            if (iCombo == 0)
-            //                ReloadPackets();
-            //        });
-            //    });
+                ITP.ItemsSource = itps;
+                ITP.SelectedValue = CoreUtils.FullGuid;
+                ITP.IsEnabled = true;
+                iCombo--;
+            }
 
-            //new Client<JobITP>().Query(
-            //   new Filter<JobITP>(x => x.Job.ID).IsEqualTo((Guid)Job.SelectedValue),
-            //   LookupFactory.DefineColumns<JobITP>(),
-            //   LookupFactory.DefineSort<JobITP>(),
-            //   (table, error) =>
-            //   {
-            //       foreach (var row in table.Rows)
-            //           ITPs[row.Get<JobITP, Guid>(x => x.ID)] = String.Format("{0}: {1}", row.Get<JobITP, String>(x => x.Code), row.Get<JobITP, String>(x => x.Description));
-            //       Dispatcher.Invoke(() =>
-            //       {
-            //           ITP.ItemsSource = ITPs;
-            //           ITP.SelectedValue = CoreUtils.FullGuid;
-            //           ITP.IsEnabled = true;
-            //           iCombo--;
-            //           if (iCombo == 0)
-            //               ReloadPackets();
-            //       });
-            //   });
+            if (newID != CoreUtils.FullGuid && ViewType == ManufacturingViewType.Job)
+            {
+                View.SelectedIndex = (int)ManufacturingViewType.Full;
+            }
+            else
+            {
+                if (!JobChanging && iCombo == 0)
+                {
+                    ReloadPackets();
+                }
+            }
         }
 
         private void Unit_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -501,17 +450,15 @@ namespace PRSDesktop
             ReloadPackets();
         }
 
-        private void CreateColumn(string title, Guid category)
+        private void AddColumn<TColumn>(string title, Guid category, TColumn column)
+            where TColumn : FrameworkElement, IManufacturingPanelColumn
         {
-            var column = new ManufacturingPanelColumn();
             column.Margin = new Thickness(_columns.Any() ? 2 : 0, 0, 0, 0);
             column.Title = title;
-            column.CompactView = CompactView;
+            column.Data = Data;
             column.Category = category;
+
             column.SetValue(Grid.ColumnProperty, _columns.Count);
-            column.Templates = Templates;
-            column.Factories = Factories;
-            column.OnChanged += Column_OnChanged;
             column.OnCollapsed += Column_OnCollapsed;
 
             Columns.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
@@ -519,13 +466,39 @@ namespace PRSDesktop
             _columns.Add(column);
         }
 
+        private void CreateColumn(string title, Guid category)
+        {
+            var job = SelectedJob;
+            if (ViewType == ManufacturingViewType.Job)
+            {
+                var column = new ManufacturingPanelJobColumn();
+                column.OnSelectJob += Column_OnSelectJob;
+                AddColumn(title, category, column);
+            }
+            else
+            {
+                var column = new ManufacturingPanelColumn();
+
+                column.CompactView = ViewType == ManufacturingViewType.Compact;
+                column.OnChanged += Column_OnChanged;
+
+                AddColumn(title, category, column);
+            }
+
+        }
+
+        private void Column_OnSelectJob(Guid jobID)
+        {
+            Job.SelectedValue = jobID;
+        }
+
         private void Column_OnCollapsed(object sender, bool collapsed)
         {
-            var index = _columns.IndexOf((ManufacturingPanelColumn)sender);
+            var index = _columns.IndexOf((IManufacturingPanelColumn)sender);
             Columns.ColumnDefinitions[index].Width = new GridLength(1, collapsed ? GridUnitType.Auto : GridUnitType.Star);
         }
 
-        private void Column_OnChanged(object sender, EventArgs e)
+        private void Column_OnChanged(object? sender, EventArgs e)
         {
             Refresh();
         }
@@ -542,11 +515,11 @@ namespace PRSDesktop
             //Kanban.Columns.Clear();
             //Kanban.Columns.Add(new KanbanColumn() { Title = "To be Issued", Categories = Guid.Empty.ToString() });
 
-            foreach (var Factory in Factories)
-                if (CurrentFactory.Equals(Guid.Empty) || CurrentFactory.Equals(Factory.ID))
-                    foreach (var Section in Sections)
-                        if (Section.Factory.ID.Equals(Factory.ID) && !Section.Hidden)
-                            CreateColumn(Section.Factory.Name + ":" + Section.Name, Section.ID);
+            foreach (var factory in Data.Factories)
+                if (CurrentFactory.Equals(Guid.Empty) || CurrentFactory.Equals(factory.ID))
+                    foreach (var section in Data.Sections)
+                        if (section.Factory.ID.Equals(factory.ID) && !section.Hidden)
+                            CreateColumn(section.Factory.Name + ":" + section.Name, section.ID);
             //Kanban.Columns.Add(new KanbanColumn() { Title = Section.Factory.Name + ":" + Section.Name, Categories = Section.ID.ToString() });
             if (bIncludeCompleted)
                 CreateColumn("Completed", CoreUtils.FullGuid);
@@ -609,17 +582,6 @@ namespace PRSDesktop
             //    CheckedKanbans.Add(task.ID);
         }
 
-
-        private string GetColor(DateTime duedate, DateTime estdate)
-        {
-            var color = "LightGreen";
-            if (duedate < estdate)
-                color = "Salmon";
-            else if (duedate < estdate.AddDays(7))
-                color = "LightYellow";
-            return color;
-        }
-
         private string GetQualityStatus(QualityStatus status)
         {
             if (status == QualityStatus.Passed)
@@ -635,29 +597,11 @@ namespace PRSDesktop
 
         private void ReloadPackets()
         {
-            Kanbans = new ObservableCollection<ManufacturingKanban>();
-            var now = DateTime.Now;
-            var elapsed = new TimeSpan(0);
-
-            var CheckedKanbans = new List<string>();
-            foreach (var column in _columns)
-                CheckedKanbans.AddRange(column.GetSelectedKanbans("").Select(x => x.ID));
-
-            var client = new Client<ManufacturingPacket>();
-
             var filter = GenerateFilter();
-
-            //new CoreTable().LoadColumns(typeof(ManufacturingPacket));
-
             var columns = GenerateColumns();
 
-            //var iprops = InABox.Core.DatabaseSchema.Properties(typeof(ManufacturingPacket)).Where(x => !x.Name.Equals("CustomAttributes") && !x.Name.Equals("Stages") && !x.Name.Equals("Time") && !x.Name.Equals("ActualTime") /* && !x.Name.Equals("TimeRemaining") */);
-
-            //foreach (var iprop in iprops)
-            //    columns.Add(iprop.Name);
-
-            now = DateTime.Now;
-            packets = client.Query(
+            using var profiler = new Profiler(false);
+            packets = new Client<ManufacturingPacket>().Query(
                 filter,
                 columns,
                 SortBy.SelectedIndex == 0
@@ -665,230 +609,22 @@ namespace PRSDesktop
                     : new SortOrder<ManufacturingPacket>(x => x.DueDate).ThenBy(x => x.Priority, SortDirection.Descending)
                         .ThenBy(x => x.SetoutLink.Number));
 
-            elapsed = DateTime.Now - now;
             Logger.Send(LogType.Information, ClientFactory.UserID,
-                string.Format("Retrieved {0} packets in {1}ms", packets.Rows.Count, elapsed.TotalMilliseconds));
-            now = DateTime.Now;
-
-            foreach (var row in packets.Rows)
-            {
-                var bOK = true;
-
-                var completed = row.Get<ManufacturingPacket, DateTime>(x => x.Completed);
-
-                var onhold = row.Get<ManufacturingPacket, bool>(x => x.OnHold);
-                var issues = row.Get<ManufacturingPacket, string>(x => x.Issues);
-
-                var orderitemid = row.EntityLinkID<ManufacturingPacket, PurchaseOrderItemLink>(x => x.OrderItem) ?? Guid.Empty;
-                var orderitem = orderitemid != Guid.Empty ? OrderItems.FirstOrDefault(x => x.Item1.Equals(orderitemid)) : null;
-                //if (bOK && !bIncludeOrders)
-                //    bOK = (orderitem == null) || !orderitem.Item2.IsEmpty();
-
-                //bool bSkipJobCheck = false;
-                var itp = row.Get<ManufacturingPacket, Guid>(x => x.ITP.ID);
-                //if (bOK && (ITP.SelectedValue != null) && !ITP.SelectedValue.Equals(CoreUtils.FullGuid))
-                //{
-                //    bOK = itp == (Guid)ITP.SelectedValue;
-                //    bSkipJobCheck = true;
-                //}
-
-                var jobid = row.Get<ManufacturingPacket, Guid>(x => x.SetoutLink.JobLink.ID);
-                //if (bOK && !bSkipJobCheck && (Job.SelectedValue != null) && !Job.SelectedValue.Equals(CoreUtils.FullGuid))
-                //    bOK = jobid ==(Guid)Job.SelectedValue;
-
-                var sectionid = row.Get<ManufacturingPacket, Guid>(x => x.StageLink.SectionID);
-                //if (bOK && !CurrentFactory.Equals(Guid.Empty))
-                //    bOK = Sections.Any(x => x.Factory.ID.Equals(CurrentFactory) && x.ID.Equals(sectionid));
-
-                //if (CurrentFactory != Guid.Empty)
-                //{
-                //    var templatefilter = new Filter<ManufacturingPacket>(x => x.ManufacturingTemplateLink.Factory.ID).IsEqualTo(CurrentFactory).Or(x => x.StageLink.ID).IsNotEqualTo(Guid.Empty);
-                //    filter.Ands.Add(templatefilter);
-                //}
-
-                var title = row.Get<ManufacturingPacket, string>(x => x.Title);
-                var serial = row.Get<ManufacturingPacket, string>(x => x.Serial);
-                var watermark = row.Get<ManufacturingPacket, string>(x => x.WaterMark);
-                var location = row.Get<ManufacturingPacket, string>(x => x.Location);
-
-                var setoutlocation = row.Get<ManufacturingPacket, string>(x => x.SetoutLink.Location);
-                var setoutnumber = row.Get<ManufacturingPacket, string>(x => x.SetoutLink.Number);
-                var setoutdescription = row.Get<ManufacturingPacket, string>(x => x.SetoutLink.Description);
-
-                var templateid = row.Get<ManufacturingPacket, Guid>(x => x.ManufacturingTemplateLink.ID);
-                var templatecode = Templates.FirstOrDefault(x => x.ID.Equals(templateid))?.Code;
-
-                //if (bOK && !String.IsNullOrWhiteSpace(SearchBox.Text))
-                //{
-                //    var search = SearchBox.Text.ToUpper();
-                //    bOK = title.ToUpper().Contains(search)
-                //        || serial.ToUpper().Contains(search)
-                //        || watermark.ToUpper().Contains(search)
-                //        || location.ToUpper().Contains(search)
-                //        || setoutlocation.ToUpper().Contains(search)
-                //        || setoutnumber.ToUpper().Contains(search)
-                //        || templatecode.ToUpper().Contains(search);
-                //}
-
-                if (bOK)
-                {
-                    var id = row.Get<ManufacturingPacket, Guid>(x => x.ID);
-                    var priority = row.Get<ManufacturingPacket, bool>(x => x.Priority);
-                    var distributed = row.Get<ManufacturingPacket, bool>(x => x.Distributed);
-
-                    var barcodeqty = row.Get<ManufacturingPacket, int>(x => x.BarcodeQty);
-                    var quantity = row.Get<ManufacturingPacket, int>(x => x.Quantity);
-                    var estimateddate = row.Get<ManufacturingPacket, DateTime>(x => x.EstimatedDate);
-                    var created = row.Get<ManufacturingPacket, DateTime>(x => x.Created);
-                    var duedate = row.Get<ManufacturingPacket, DateTime>(x => x.DueDate);
-                    var barcodeprinted = row.Get<ManufacturingPacket, DateTime>(x => x.BarcodePrinted);
-                    var barcodetype = row.Get<ManufacturingPacket, BarcodeType>(x => x.BarcodeType);
-
-                    var stageid = row.Get<ManufacturingPacket, Guid>(x => x.StageLink.ID);
-                    var stageValid = Entity.IsEntityLinkValid<ManufacturingPacket, ManufacturingPacketStageLink>(x => x.StageLink, row);
-                    var station = row.Get<ManufacturingPacket, int>(x => x.StageLink.Station);
-                    var time = row.Get<ManufacturingPacket, TimeSpan>(x => x.StageLink.Time);
-                    var percentagecomplete = row.Get<ManufacturingPacket, double>(x => x.StageLink.PercentageComplete);
-
-                    var jobrow = Jobs.Rows.FirstOrDefault(r => r.Get<Job, Guid>(c => c.ID).Equals(jobid));
-                    var jobname = jobrow?.Get<Job, string>(c => c.Name);
-                    var jobnumber = jobrow?.Get<Job, string>(c => c.JobNumber);
-
-                    var model = new ManufacturingKanban();
-
-                    var flags = new List<string>();
-                    if (onhold)
-                        flags.Add("HOLD");
-                    if (priority)
-                        flags.Add("PRIORITY");
-                    if (distributed)
-                        flags.Add("DISTRIB");
-
-
-                    model.ID = id.ToString();
-
-                    var sTitle = string.Format("{0}{1}", quantity != barcodeqty ? string.Format("{0} x ", quantity) : "",
-                        row.Get<ManufacturingPacket, string>(x => x.Title));
-
-                    model.Title = CompactView
-                        ? string.Format("{0} x {1} / {2} {3}",
-                            barcodeqty,
-                            setoutnumber,
-                            serial,
-                            sTitle
-                        )
-                        : string.Format("{0}: {1}",
-                            serial,
-                            sTitle
-                        );
-
-                    if (!string.IsNullOrWhiteSpace(watermark))
-                        model.Title = "[" + watermark + "] " + model.Title;
-
-                    model.Quantity = barcodeqty;
-
-                    model.JobName = string.Format("{0}: {1}", setoutnumber, jobname);
-
-                    model.CreatedDate = created;
-
-                    model.DueDate = duedate;
-
-                    model.Time = time;
-                    model.PercentageComplete = percentagecomplete;
-
-                    if (string.IsNullOrEmpty(location))
-                        location = setoutlocation;
-
-                    var descrip = new List<string>();
-
-                    //List<String> locdesc = new List<string>()
-                    //{
-                    //    row.Get<ManufacturingPacket,String>(x=>x.Level.Code),
-                    //    row.Get<ManufacturingPacket,String>(x=>x.Zone.Code),
-                    //    location
-                    //};
-                    //descrip.Add(String.Join(" / ", locdesc.Where(x => !String.IsNullOrWhiteSpace(x))));
-                    descrip.Add(location);
-
-                    if (orderitem != null)
-                        descrip.Add(orderitem.Item3);
-
-                    model.Description = string.Join("\n", descrip);
-
-                    model.TemplateID = row.Get<ManufacturingPacket, Guid>(c => c.ManufacturingTemplateLink.ID);
+                string.Format("Retrieved {0} packets in {1}ms", packets.Rows.Count, profiler.Restart()));
 
-                    model.Image = !barcodeprinted.IsEmpty() ? barcode : barcodetype == BarcodeType.None ? disabled : null;
-                    model.Tags = new string[] { };
-                    model.Category = completed != DateTime.MinValue ? CoreUtils.FullGuid : sectionid;
+            var results = packets
+                .ToObjects<ManufacturingPacket>()
+                .GroupBy(x => x.StageLink.SectionID)
+                .ToDictionary(x => x.Key, x => x);
 
-                    if (priority)
-                        model.ColorKey = "Red";
-                    else if (onhold)
-                        model.ColorKey = "Silver";
-                    else
-                        model.ColorKey = GetColor(
-                            duedate.IsEmpty() ? DateTime.Today : duedate,
-                            estimateddate.IsEmpty() ? DateTime.Today : estimateddate
-                        );
-                    model.IssuesImage = string.IsNullOrWhiteSpace(issues)
-                        ? null
-                        : speechbubble;
-                    model.Issues = issues;
-
-                    if (orderitem != null)
-                    {
-                        var bOnOrder = orderitem.Item2.IsEmpty();
-                        model.OrderColor = bOnOrder ? "Plum" : "DarkOrchid";
-                        model.OrderStatus = bOnOrder ? "ON ORDER" : "RECEIVED";
-                    }
-                    else
-                    {
-                        model.OrderColor = model.ColorKey;
-                        model.OrderStatus = "";
-                    }
-
-                    model.Checked = CheckedKanbans.Contains(id.ToString());
-
-                    model.Flags = string.Join("\n", flags);
-
-                    model.Template = templatecode;
-
-
-                    if (!stageValid || stageid.Equals(Guid.Empty) || stageid.Equals(CoreUtils.FullGuid))
-                    {
-                        model.Status = "";
-                    }
-                    else
-                    {
-                        if (station == 0)
-                            model.Status = "Not Started";
-                        else
-                            model.Status = string.Format("{0} ({1:F0}%)", station == -1 ? "Shared" : "Stn " + station, percentagecomplete);
-                    }
-
-                    Kanbans.Add(model);
-                }
-            }
-
-            elapsed = DateTime.Now - now;
-            Logger.Send(LogType.Information, ClientFactory.UserID, string.Format("Processed Kanbans in {0}ms", elapsed.TotalMilliseconds));
-            now = DateTime.Now;
+            Logger.Send(LogType.Information, ClientFactory.UserID, string.Format("Processed Kanbans in {0}ms", profiler.Restart()));
 
             foreach (var column in _columns)
             {
-                column.packets = packets;
-                column.Kanbans = Kanbans.Where(x => x.Category.Equals(column.Category)).ToArray();
+                column.SetPackets(results.GetValueOrDefault(column.Category) ?? Enumerable.Empty<ManufacturingPacket>());
             }
-            //foreach (var col in Kanban.Columns)
-            //    col.IsExpanded = Kanbans.Where(x => col.Categories.Contains(x.Category.ToString())).Count() > 0;
-
-            //bResizeRequired = true;
 
-            //Kanban.ItemsSource = null;
-            //Kanban.ItemsSource = Kanbans;
-
-            elapsed = DateTime.Now - now;
-            Logger.Send(LogType.Information, ClientFactory.UserID, string.Format("Loaded Columns in {0}ms", elapsed.TotalMilliseconds));
+            Logger.Send(LogType.Information, ClientFactory.UserID, string.Format("Loaded Columns in {0}ms", profiler.Restart()));
         }
 
         private Filter<ManufacturingPacket> GenerateFilter()
@@ -922,8 +658,8 @@ namespace PRSDesktop
 
             var sctflt = new Filter<ManufacturingPacket>(x => x.StageLink).NotLinkValid();
 
-            foreach (var Section in Sections.Where(x => CurrentFactory.Equals(Guid.Empty) || x.Factory.ID.Equals(CurrentFactory)))
-                sctflt = sctflt.Or(x => x.StageLink.SectionID).IsEqualTo(Section.ID);
+            foreach (var section in Data.Sections.Where(x => CurrentFactory.Equals(Guid.Empty) || x.Factory.ID.Equals(CurrentFactory)))
+                sctflt = sctflt.Or(x => x.StageLink.SectionID).IsEqualTo(section.ID);
             filter.Ands.Add(sctflt);
 
             if (CurrentFactory != Guid.Empty)
@@ -1007,71 +743,54 @@ namespace PRSDesktop
 
         private void Factories_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
-            if (e.AddedItems.Count == 0)
+            if (e.AddedItems.Count == 0 || e.AddedItems[0] is not Tuple<string, BitmapImage, Guid> selected)
                 return;
 
-            var selected = (Tuple<string, BitmapImage, Guid>)e.AddedItems[0];
             CurrentFactory = selected.Item3;
 
-
             if (IsReady)
                 SaveSettings();
 
-            foreach (var column in _columns)
-                column.ClearSelectedKanbans();
-            //column.CheckedKanbans.Clear();
-            //CheckedKanbans.Clear();
             if (IsReady)
                 Refresh();
         }
 
         private void SortBy_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
-            if (e.AddedItems.Count == 0)
+            if (e.AddedItems.Count == 0 || e.AddedItems[0] is not ComboBoxItem selected)
                 return;
 
-            var selected = (ComboBoxItem)e.AddedItems[0];
             SortByDueDate = selected.Content.Equals("Due Date");
 
-
             if (IsReady)
                 SaveSettings();
 
-            foreach (var column in _columns)
-                column.ClearSelectedKanbans();
-            //column.CheckedKanbans.Clear();
-            //CheckedKanbans.Clear();
             if (IsReady)
                 Refresh();
         }
 
         private void View_SelectionChanged(object sender, SelectionChangedEventArgs e)
         {
-            if (e.AddedItems.Count == 0)
+            if (e.AddedItems.Count == 0 || e.AddedItems[0] is not ComboBoxItem selected)
                 return;
 
-            var selected = (ComboBoxItem)e.AddedItems[0];
-            CompactView = selected.Content.Equals("Compact");
-            ConsolidatedView = selected.Content.Equals("Consolidated");
+            ViewType = (ManufacturingViewType)View.SelectedIndex;
 
             if (IsReady)
                 SaveSettings();
 
-            foreach (var column in _columns)
-                column.ClearSelectedKanbans();
-            //column.CheckedKanbans.Clear();
-            //CheckedKanbans.Clear();
+            if (ViewType == ManufacturingViewType.Job && SelectedJob != CoreUtils.FullGuid)
+            {
+                SelectedJob = CoreUtils.FullGuid;
+                JobChanging = true;
+                Job.SelectedValue = SelectedJob;
+                JobChanging = false;
+            }
 
             if (IsReady)
                 Refresh();
         }
 
-        private ManufacturingPacket[] GetSelectedPackets(string currentid)
-        {
-            var rows = packets.Rows.Where(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).Equals(Guid.Parse(currentid)));
-            return rows.Select(r => r.ToObject<ManufacturingPacket>()).ToArray();
-        }
-
         private void SearchBox_KeyUp(object sender, KeyEventArgs e)
         {
             if (string.IsNullOrWhiteSpace(SearchBox.Text) || e.Key == Key.Return)
@@ -1114,8 +833,7 @@ namespace PRSDesktop
             {
                 FactoryID = CurrentFactory,
                 SortByDueDate = SortByDueDate,
-                CompactView = CompactView,
-                ConsolidatedView = ConsolidatedView,
+                ViewType = ViewType,
                 IncludeHeld = bIncludeHeld,
                 IncludeOrders = bIncludeOrders,
                 IncludeCompleted = bIncludeCompleted
@@ -1125,7 +843,7 @@ namespace PRSDesktop
 
         private void Export_Click(object sender, RoutedEventArgs e)
         {
-            IEnumerable<string> columns = string.IsNullOrWhiteSpace(settings.ExportColumns) ? new string[] { } : settings.ExportColumns.Split(',');
+            IEnumerable<string> columns = string.IsNullOrWhiteSpace(settings.ExportColumns) ? Array.Empty<string>() : settings.ExportColumns.Split(',');
             if (!columns.Any())
                 columns = packets.Columns.Select(x => x.ColumnName);
 

+ 248 - 89
prs.desktop/Panels/Manufacturing/ManufacturingPanelColumn.xaml.cs

@@ -7,6 +7,7 @@ using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Input;
+using System.Windows.Media.Imaging;
 using Comal.Classes;
 using InABox.Clients;
 using InABox.Core;
@@ -53,13 +54,17 @@ namespace PRSDesktop
     /// <summary>
     ///     Interaction logic for ManufacturingPanelColumn.xaml
     /// </summary>
-    public partial class ManufacturingPanelColumn : UserControl
+    public partial class ManufacturingPanelColumn : UserControl, IManufacturingPanelColumn
     {
+        private static readonly BitmapImage barcode = PRSDesktop.Resources.barcode.AsBitmapImage();
+        private static readonly BitmapImage disabled = PRSDesktop.Resources.disabled.AsBitmapImage();
+        private static readonly BitmapImage speechbubble = PRSDesktop.Resources.speechbubble.AsBitmapImage();
+
         private bool _collapsed;
 
         private bool _compactview;
 
-        private DynamicDataGrid<ManufacturingPacket> pg;
+        private DynamicDataGrid<ManufacturingPacket>? pg;
 
         //public List<String> CheckedKanbans = new List<string>();
 
@@ -85,9 +90,9 @@ namespace PRSDesktop
                 {
                     TemplateViewModel model = new TemplateViewModel
                     {
-                        TemplateID = Guid.Parse(row.Values[0].ToString()),
-                        Name = row.Values[1].ToString(),
-                        Time = row.Values[2].ToString()
+                        TemplateID = row.Get<ManufacturingTemplateStage, Guid>(x => x.Template.ID),
+                        Name = row.Get<ManufacturingTemplateStage, string>(x => x.Section.Name),
+                        Time = row.Get<ManufacturingTemplateStage, TimeSpan>(x => x.Time).ToString()
                     };
                     templateStages.Add(model);
                 }
@@ -156,27 +161,205 @@ namespace PRSDesktop
             }
         }
 
-        public CoreTable packets { get; set; }
-
-        private List<TemplateViewModel> templateStages = new List<TemplateViewModel>();
+        public ManufacturingPanelData Data { get; set; }
 
-        public ManufacturingFactory[] Factories { get; set; }
+        public List<ManufacturingPacket> Packets { get; set; }
 
-        public ManufacturingTemplate[] Templates { get; set; }
+        private List<TemplateViewModel> templateStages = new List<TemplateViewModel>();
 
         private ManufacturingTemplateStage[] TemplateStages { get; set; }
 
         private double CalcTime(IEnumerable<ManufacturingKanban> kanbans)
         {
             return Kanbans.Sum(x => x.Time.TotalHours * ((100.0F - x.PercentageComplete) / 100.0F));
-            //var rows = packets.Rows.Where(r => kanbans.Any(k => r.Get<ManufacturingPacket, Guid>(c => c.ID).ToString().Equals(k.ID)));
-            //double hours = rows.Sum(x => x.Get<ManufacturingPacket, TimeSpan>(c => c.TimeRemaining).TotalHours);
-            //return hours;
         }
 
         public event EventHandler OnChanged;
         public event CollapsingEventHandler OnCollapsed;
 
+        private static string GetColor(DateTime duedate, DateTime estdate)
+        {
+            var color = "LightGreen";
+            if (duedate < estdate)
+                color = "Salmon";
+            else if (duedate < estdate.AddDays(7))
+                color = "LightYellow";
+            return color;
+        }
+
+        public void SetPackets(IEnumerable<ManufacturingPacket> packets)
+        {
+            var checkedKanbans = GetSelectedKanbans("").Select(x => x.ID).ToList();
+
+            var kanbans = new List<ManufacturingKanban>();
+            Packets = packets.ToList();
+            foreach (var packet in Packets)
+            {
+                var bOK = true;
+
+                var completed = packet.Completed;
+
+                var onhold = packet.OnHold;
+                var issues = packet.Issues;
+
+                var orderitemid = packet.OrderItem.ID;
+                var orderitem = orderitemid != Guid.Empty ? Data.OrderItems.FirstOrDefault(x => x.Item1.Equals(orderitemid)) : null;
+
+                var itp = packet.ITP.ID;
+
+                var jobid = packet.SetoutLink.JobLink.ID;
+
+                var sectionid = packet.StageLink.SectionID;
+
+                var title = packet.Title;
+                var serial = packet.Serial;
+                var watermark = packet.WaterMark;
+                var location = packet.Location;
+
+                var setoutlocation = packet.SetoutLink.Location;
+                var setoutnumber = packet.SetoutLink.Number;
+                var setoutdescription = packet.SetoutLink.Description;
+
+                var templateid = packet.ManufacturingTemplateLink.ID;
+                var templatecode = Data.Templates.FirstOrDefault(x => x.ID.Equals(templateid))?.Code;
+
+                if (bOK)
+                {
+                    var id = packet.ID;
+                    var priority = packet.Priority;
+                    var distributed = packet.Distributed;
+
+                    var barcodeqty = packet.BarcodeQty;
+                    var quantity = packet.Quantity;
+                    var estimateddate = packet.EstimatedDate;
+                    var created = packet.Created;
+                    var duedate = packet.DueDate;
+                    var barcodeprinted = packet.BarcodePrinted;
+                    var barcodetype = packet.BarcodeType;
+
+                    var stageid = packet.StageLink.ID;
+                    var stageValid = packet.StageLink.IsValid();
+                    var station = packet.StageLink.Station;
+                    var time = packet.StageLink.Time;
+                    var percentagecomplete = packet.StageLink.PercentageComplete;
+
+                    var jobrow = Data.Jobs.Rows.FirstOrDefault(r => r.Get<Job, Guid>(c => c.ID).Equals(jobid));
+                    var jobname = jobrow?.Get<Job, string>(c => c.Name);
+                    var jobnumber = jobrow?.Get<Job, string>(c => c.JobNumber);
+
+                    var model = new ManufacturingKanban();
+
+                    var flags = new List<string>();
+                    if (onhold)
+                        flags.Add("HOLD");
+                    if (priority)
+                        flags.Add("PRIORITY");
+                    if (distributed)
+                        flags.Add("DISTRIB");
+
+
+                    model.ID = id.ToString();
+
+                    var sTitle = string.Format("{0}{1}", quantity != barcodeqty ? string.Format("{0} x ", quantity) : "",
+                        packet.Title);
+
+                    model.Title = CompactView
+                        ? string.Format("{0} x {1} / {2} {3}",
+                            barcodeqty,
+                            setoutnumber,
+                            serial,
+                            sTitle
+                        )
+                        : string.Format("{0}: {1}",
+                            serial,
+                            sTitle
+                        );
+
+                    if (!string.IsNullOrWhiteSpace(watermark))
+                        model.Title = "[" + watermark + "] " + model.Title;
+
+                    model.Quantity = barcodeqty;
+
+                    model.JobName = string.Format("{0}: {1}", setoutnumber, jobname);
+
+                    model.CreatedDate = created;
+
+                    model.DueDate = duedate;
+
+                    model.Time = time;
+                    model.PercentageComplete = percentagecomplete;
+
+                    if (string.IsNullOrEmpty(location))
+                        location = setoutlocation;
+
+                    var descrip = new List<string>
+                    {
+                        location
+                    };
+
+                    if (orderitem != null)
+                        descrip.Add(orderitem.Item3);
+
+                    model.Description = string.Join("\n", descrip);
+
+                    model.TemplateID = packet.ManufacturingTemplateLink.ID;
+
+                    model.Image = !barcodeprinted.IsEmpty() ? barcode : barcodetype == BarcodeType.None ? disabled : null;
+                    model.Tags = new string[] { };
+                    model.Category = completed != DateTime.MinValue ? CoreUtils.FullGuid : sectionid;
+
+                    if (priority)
+                        model.ColorKey = "Red";
+                    else if (onhold)
+                        model.ColorKey = "Silver";
+                    else
+                        model.ColorKey = GetColor(
+                            duedate.IsEmpty() ? DateTime.Today : duedate,
+                            estimateddate.IsEmpty() ? DateTime.Today : estimateddate
+                        );
+                    model.IssuesImage = string.IsNullOrWhiteSpace(issues)
+                        ? null
+                        : speechbubble;
+                    model.Issues = issues;
+
+                    if (orderitem != null)
+                    {
+                        var bOnOrder = orderitem.Item2.IsEmpty();
+                        model.OrderColor = bOnOrder ? "Plum" : "DarkOrchid";
+                        model.OrderStatus = bOnOrder ? "ON ORDER" : "RECEIVED";
+                    }
+                    else
+                    {
+                        model.OrderColor = model.ColorKey;
+                        model.OrderStatus = "";
+                    }
+
+                    model.Checked = checkedKanbans.Contains(id.ToString());
+
+                    model.Flags = string.Join("\n", flags);
+
+                    model.Template = templatecode;
+
+
+                    if (!stageValid || stageid.Equals(Guid.Empty) || stageid.Equals(CoreUtils.FullGuid))
+                    {
+                        model.Status = "";
+                    }
+                    else
+                    {
+                        if (station == 0)
+                            model.Status = "Not Started";
+                        else
+                            model.Status = string.Format("{0} ({1:F0}%)", station == -1 ? "Shared" : "Stn " + station, percentagecomplete);
+                    }
+
+                    kanbans.Add(model);
+                }
+            }
+
+            Kanbans = kanbans.ToArray();
+        }
+
         public void ClearSelectedKanbans()
         {
             var kanbans = GetSelectedKanbans(Guid.Empty.ToString());
@@ -192,40 +375,31 @@ namespace PRSDesktop
             return Kanbans != null ? Kanbans.Where(x => x.Checked || x.ID.Equals(currentid)).ToArray() : new ManufacturingKanban[] { };
         }
 
-        public CoreRow[] GetSelectedRows(string currentid)
-        {
-            var selectedkanbans = GetSelectedKanbans(currentid);
-            return packets.Rows.Where(row => selectedkanbans.Any(x => x.ID.Equals(row.Get<ManufacturingPacket, Guid>(col => col.ID).ToString())))
-                .ToArray();
-        }
+        public IEnumerable<ManufacturingPacket> GetPackets() => Packets;
 
-        private ManufacturingPacket[] GetSelectedPackets(string currentid)
+        public IEnumerable<ManufacturingPacket> GetSelectedPackets() => GetSelectedPackets(Guid.Empty.ToString());
+
+        public IEnumerable<ManufacturingPacket> GetSelectedPackets(string currentid)
         {
-            var rows = GetSelectedRows(currentid);
-            //var rows = packets.Rows.Where(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).Equals(Guid.Parse(currentid)) || CheckedKanbans.Contains(r.Get<ManufacturingPacket, Guid>(c => c.ID).ToString()));
-            var result = rows.Select(r => r.ToObject<ManufacturingPacket>()).ToArray();
-            Logger.Send(LogType.Information, ClientFactory.UserID,
-                string.Format("ManufacturingPanelColumn.GetSelectedPackets() [{0}] {1}", result.Length,
-                    string.Join(", ", result.Select(x => x.Serial))));
-            return result;
+            var selectedkanbans = GetSelectedKanbans(currentid);
+            return Packets.Where(row => selectedkanbans.Any(x => x.ID.Equals(x.ID.ToString())));
         }
 
-
         private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
         {
             if (e.ClickCount == 2)
             {
                 var model = ((Border)sender).Tag as ManufacturingKanban;
-                var row = packets.Rows.Where(r => r.Get<ManufacturingPacket, Guid>(x => x.ID).ToString().Equals(model.ID)).FirstOrDefault();
+                var packet = Packets.Where(r => r.ID.ToString().Equals(model.ID)).FirstOrDefault();
 
-                var setoutid = row.Get<ManufacturingPacket, Guid>(x => x.SetoutLink.ID);
+                var setoutid = packet.SetoutLink.ID;
                 var table = new Client<SetoutDocument>().Query(new Filter<SetoutDocument>(x => x.EntityLink.ID).IsEqualTo(setoutid));
                 IEntityDocument[] docs = table.Rows.Select(r => r.ToObject<SetoutDocument>()).ToArray();
                 var viewer = new DocumentEditor(docs);
                 viewer.ButtonsVisible = true;
                 //viewer.PrintAllowed = Security.IsAllowed<CanPrintFactoryFloorDrawings>();
                 viewer.SaveAllowed = Security.IsAllowed<CanSaveFactoryFloorDrawings>();
-                viewer.Watermark = row.Get<ManufacturingPacket, string>(x => x.WaterMark);
+                viewer.Watermark = packet.WaterMark;
                 viewer.ShowDialog();
 
                 e.Handled = true;
@@ -286,12 +460,12 @@ namespace PRSDesktop
 
             var model = menu.Tag as ManufacturingKanban;
 
-            var packets = GetSelectedPackets(model.ID);
+            var packets = GetSelectedPackets(model.ID).ToList();
             var current = packets.FirstOrDefault(x => x.ID.Equals(Guid.Parse(model.ID)));
 
             //CoreRow row = packets.Rows.Where(r => r.Get<ManufacturingPacket, Guid>(c => c.ID).ToString().Equals(model.ID)).FirstOrDefault();
 
-            var OnlyOne = packets.Length == 1; // (!CheckedKanbans.Any()) || (CheckedKanbans.Count == 1) && (CheckedKanbans.Contains(model.ID));
+            var OnlyOne = packets.Count == 1; // (!CheckedKanbans.Any()) || (CheckedKanbans.Count == 1) && (CheckedKanbans.Contains(model.ID));
 
             var pktid = current != null ? current.ID : Guid.Empty; // row.Get<ManufacturingPacket, Guid>(x => x.ID);
             var stageid = current != null ? current.StageLink.ID : Guid.Empty; // row.Get<ManufacturingPacket, Guid>(x => x.StageLink.ID);
@@ -321,7 +495,7 @@ namespace PRSDesktop
             template.Visibility = !stageValid ? Visibility.Visible : Visibility.Collapsed;
             template.Items.Clear();
             var menus = new Dictionary<string, MenuItem>();
-            foreach (var Factory in Factories)
+            foreach (var Factory in Data.Factories)
             {
                 if (!menus.ContainsKey(Factory.Name))
                 {
@@ -330,7 +504,7 @@ namespace PRSDesktop
                 }
 
                 var group = menus[Factory.Name];
-                foreach (var temp in Templates.Where(x => x.Factory.ID == Factory.ID).OrderBy(x => x.Code))
+                foreach (var temp in Data.Templates.Where(x => x.Factory.ID == Factory.ID).OrderBy(x => x.Code))
                 {
                     var item = new MenuItem
                     {
@@ -478,53 +652,38 @@ namespace PRSDesktop
         {
             var item = (MenuItem)sender;
             var model = (ManufacturingKanban)item.Tag;
-            var row = packets.Rows.Where(r => r.Get<ManufacturingPacket, Guid>(x => x.ID).ToString().Equals(model.ID)).FirstOrDefault();
+            var pkt = Packets.Where(r => r.ID.ToString().Equals(model.ID)).FirstOrDefault();
 
-            if (row == null)
+            if (pkt == null)
             {
                 MessageBox.Show("Cannot find selected Manufacturing Packet!");
                 return;
             }
 
-            var id = row.Get<ManufacturingPacket, Guid>(c => c.ID);
+            var id = pkt.ID;
 
-            if (pg == null)
-                pg = new DynamicDataGrid<ManufacturingPacket>();
+            pg ??= new DynamicDataGrid<ManufacturingPacket>();
 
-            ManufacturingPacket packet = null;
+            ManufacturingPacket? editPacket = null;
             using (new WaitCursor())
             {
                 var columns = pg.LoadEditorColumns();
 
-                packet = new Client<ManufacturingPacket>()
+                editPacket = new Client<ManufacturingPacket>()
                     .Query(
                         new Filter<ManufacturingPacket>(x => x.ID).IsEqualTo(id),
                         columns)
                     .Rows.FirstOrDefault()?.ToObject<ManufacturingPacket>();
             }
 
-            if (packet == null)
+            if (editPacket == null)
             {
                 MessageBox.Show("Unable to load packet from database");
                 return;
             }
 
-            //ManufacturingPacket packet = row.ToObject<ManufacturingPacket>();
-            if (pg.EditItems(new[] { packet }))
+            if (pg.EditItems(new[] { editPacket }))
                 OnChanged?.Invoke(this, new EventArgs());
-
-            //Setout setout = null;
-            //using (new WaitCursor())
-            //    setout = new Client<Setout>().Load(new Filter<Setout>(x => x.ID).IsEqualTo(row.Get<ManufacturingPacket, Guid>(x => x.SetoutLink.ID))).FirstOrDefault();
-
-            //if (setout != null)
-            //{
-            //    var grid = new DynamicDataGrid<Setout>();
-            //    if (grid.EditItems(new Setout[] { setout }))
-            //        OnChanged?.Invoke(this, new EventArgs());
-            //}
-            //else
-            //    MessageBox.Show("No setout for packet!");
         }
 
         private void IssueSetout_Click(object sender, RoutedEventArgs e)
@@ -547,10 +706,10 @@ namespace PRSDesktop
         private void ProgressPacket(ManufacturingKanban model, DateTime? date)
         {
             Progress.Show("");
-            var pkts = GetSelectedPackets(model.ID);
+            var pkts = GetSelectedPackets(model.ID).ToList();
 
             Progress.SetMessage("Loading Stages");
-            Filter<ManufacturingPacketStage> stgflt = null;
+            Filter<ManufacturingPacketStage>? stgflt = null;
             foreach (var pkt in pkts)
                 stgflt = stgflt == null
                     ? new Filter<ManufacturingPacketStage>(x => x.Parent.ID).IsEqualTo(pkt.ID)
@@ -571,7 +730,7 @@ namespace PRSDesktop
                 foreach (var pkt in pktupdate)
                     pkt.DueDate = date.Value;
             Logger.Send(LogType.Information, ClientFactory.UserID,
-                string.Format("ManufacturingPanelColumn.ProgressPacket() - Updating [{0}] packets: {1}", pktupdate.Length,
+                string.Format("ManufacturingPanelColumn.ProgressPacket() - Updating [{0}] packets: {1}", pktupdate.Count,
                     string.Join(", ", pktupdate.Select(x => x.Serial))));
             new Client<ManufacturingPacket>().Save(pktupdate, "ManufacturingPacket Progressed by Menu Click");
 
@@ -588,35 +747,36 @@ namespace PRSDesktop
             var item = (MenuItem)sender;
             var model = (ManufacturingKanban)item.Tag;
 
-            var pkts = GetSelectedPackets(model.ID);
-            if (pkts.Length != 1)
+            var pkts = GetSelectedPackets(model.ID).ToList();
+            if (pkts.Count != 1)
             {
                 MessageBox.Show("You can only split one Packet at a time!");
                 return;
             }
+            var pkt = pkts.First();
 
 
-            var packets = new List<ManufacturingPacket> { pkts.First() };
+            var packets = new List<ManufacturingPacket> { pkt };
             var Quantity = 1;
-            if (NumberEdit.Execute("Quantity to Issue", 1, pkts.First().Quantity, ref Quantity))
+            if (NumberEdit.Execute("Quantity to Issue", 1, pkt.Quantity, ref Quantity))
             {
-                if (Quantity != pkts.First().Quantity)
+                if (Quantity != pkt.Quantity)
                 {
-                    var packet2 = CoreUtils.Clone(pkts.First(), null);
+                    var packet2 = CoreUtils.Clone(pkt, null);
                     packet2.ID = Guid.Empty;
                     packet2.Quantity = Quantity;
-                    pkts.First().Quantity = pkts.First().Quantity - Quantity;
+                    pkt.Quantity -= Quantity;
                     packets.Add(packet2);
                 }
 
                 new Client<ManufacturingPacket>().Save(packets, "Split Manufacturing Packet");
 
                 var stages = new Client<ManufacturingPacketStage>().Load(
-                    new Filter<ManufacturingPacketStage>(x => x.Parent.ID).IsEqualTo(pkts.First().ID));
+                    new Filter<ManufacturingPacketStage>(x => x.Parent.ID).IsEqualTo(pkt.ID));
                 foreach (var stage in stages)
                 {
                     stage.ID = Guid.Empty;
-                    stage.Parent.ID = pkts.First().ID;
+                    stage.Parent.ID = pkt.ID;
                 }
 
                 new Client<ManufacturingPacketStage>().Save(stages, "");
@@ -630,9 +790,9 @@ namespace PRSDesktop
             var item = (MenuItem)sender;
             var model = (ManufacturingKanban)item.Tag;
             Progress.Show("");
-            var pkts = GetSelectedPackets(model.ID);
+            var pkts = GetSelectedPackets(model.ID).ToList();
 
-            Filter<ManufacturingPacketStage> stgflt = null;
+            Filter<ManufacturingPacketStage>? stgflt = null;
             foreach (var pkt in pkts)
                 stgflt = stgflt == null
                     ? new Filter<ManufacturingPacketStage>(x => x.Parent.ID).IsEqualTo(pkt.ID)
@@ -663,9 +823,8 @@ namespace PRSDesktop
                 Progress.Show("");
                 var pkts = GetSelectedPackets(model.ID);
                 var updates = new List<ManufacturingPacket>();
-                for (var i = 0; i < pkts.Length; i++)
+                foreach (var packet in pkts)
                 {
-                    var packet = pkts[i];
                     packet.BarcodePrinted = DateTime.MinValue;
                     packet.Archived = DateTime.Now;
                     updates.Add(packet);
@@ -689,7 +848,7 @@ namespace PRSDesktop
             var pkts = GetSelectedPackets(model.ID);
 
             Progress.SetMessage("Loading Stages");
-            Filter<ManufacturingPacketStage> stgflt = null;
+            Filter<ManufacturingPacketStage>? stgflt = null;
             foreach (var pkt in pkts)
                 stgflt = stgflt == null
                     ? new Filter<ManufacturingPacketStage>(x => x.Parent.ID).IsEqualTo(pkt.ID)
@@ -726,7 +885,7 @@ namespace PRSDesktop
                     date = pkt.DueDate;
                 else if (!date.Value.Equals(pkt.DueDate))
                     date = date > pkt.DueDate ? date : pkt.DueDate;
-            var date2 = !date.HasValue ? DateTime.Today.AddDays(14) : date.Value;
+            var date2 = date ?? DateTime.Today.AddDays(14) ;
             if (DateEdit.Execute("Required Completion Date", ref date2))
             {
                 Progress.SetMessage("Updating Packets");
@@ -746,15 +905,14 @@ namespace PRSDesktop
             var item = (MenuItem)sender;
             var model = (ManufacturingKanban)item.Tag;
             Progress.Show("");
-            var pkts = GetSelectedPackets(model.ID);
-            for (var i = 0; i < pkts.Length; i++)
+            var pkts = GetSelectedPackets(model.ID).ToList();
+            foreach(var packet in pkts)
             {
-                var packet = pkts[i];
                 packet.Priority = priority;
             }
 
             Logger.Send(LogType.Information, ClientFactory.UserID,
-                string.Format("ManufacturingPanelColumn.UpdatePriority() - Updating [{0}] packets: {1}", pkts.Length,
+                string.Format("ManufacturingPanelColumn.UpdatePriority() - Updating [{0}] packets: {1}", pkts.Count,
                     string.Join(", ", pkts.Select(x => x.Serial))));
 
             Progress.SetMessage("Updating Packets");
@@ -795,14 +953,16 @@ namespace PRSDesktop
                )
                 return;
 
-            Progress.ShowModal("Updating Hold Flags", progress =>
+            Progress.ShowModal("Updating Hold Flags", (Action<IProgress<string>>)(progress =>
             {
-                var pkts = GetSelectedPackets(model.ID);
-                for (var i = 0; i < pkts.Length; i++)
-                    pkts[i].OnHold = hold;
+                var pkts = GetSelectedPackets(model.ID).Select(x =>
+                {
+                    x.OnHold = hold;
+                    return x;
+                });
                 progress.Report("Updating Packets");
                 new Client<ManufacturingPacket>().Save(pkts, "Hold Flag " + (hold ? "Set" : "Cleared"));
-            });
+            }));
 
             ClearSelectedKanbans();
 
@@ -813,7 +973,7 @@ namespace PRSDesktop
         {
             var item = (MenuItem)sender;
             var model = (ManufacturingKanban)item.Tag;
-            var pkts = GetSelectedPackets(model.ID);
+            var pkts = GetSelectedPackets(model.ID).ToArray();
             if (new DynamicIssuesEditor(pkts, true).ShowDialog() == true)
             {
                 Progress.ShowModal("Updating Issues", progress => { new Client<ManufacturingPacket>().Save(pkts, "Updated Issues"); });
@@ -973,11 +1133,10 @@ namespace PRSDesktop
                 var item = (MenuItem)sender;
                 var model = (ManufacturingKanban)item.Tag;
                 Progress.Show("");
-                var pkts = GetSelectedPackets(model.ID);
+
                 var updates = new List<ManufacturingPacket>();
-                for (var i = 0; i < pkts.Length; i++)
+                foreach(var packet in GetSelectedPackets(model.ID))
                 {
-                    var packet = pkts[i];
                     if (!packet.BarcodePrinted.IsEmpty())
                     {
                         //while (packet.Completed.IsEmpty())

+ 25 - 0
prs.desktop/Panels/Manufacturing/ManufacturingPanelData.cs

@@ -0,0 +1,25 @@
+using Comal.Classes;
+using InABox.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PRSDesktop
+{
+    public class ManufacturingPanelData
+    {
+        public readonly List<Tuple<Guid, DateTime, string>> OrderItems = new();
+
+        public CoreTable Jobs;
+
+        public CoreTable ITPs;
+
+        public ManufacturingTemplate[] Templates = Array.Empty<ManufacturingTemplate>();
+
+        public ManufacturingFactory[] Factories = Array.Empty<ManufacturingFactory>();
+
+        public ManufacturingSection[] Sections = Array.Empty<ManufacturingSection>();
+    }
+}

+ 293 - 0
prs.desktop/Panels/Manufacturing/ManufacturingPanelJobColumn.xaml

@@ -0,0 +1,293 @@
+<UserControl x:Class="PRSDesktop.ManufacturingPanelJobColumn"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PRSDesktop"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <UserControl.Resources>
+
+        <local:PercentGridLengthConverter x:Key="PercentGridLengthConverter" />
+
+        <DataTemplate x:Key="FullManufacturingPanel" DataType="{x:Type local:ManufacturingJobModel}">
+            <Grid Margin="0,1,0,1">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*" />
+                </Grid.ColumnDefinitions>
+                
+                <Border Grid.Column="0" BorderBrush="Gray" BorderThickness="0,0.75,0.75,0.75" CornerRadius="0,5,5,0"
+                        Tag="{Binding JobID}"
+                        MouseLeftButtonDown="Border_MouseLeftButtonDown">
+                    <Grid Margin="3">
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="Auto" />
+                            <ColumnDefinition Width="*" />
+                            <ColumnDefinition Width="Auto" />
+                        </Grid.ColumnDefinitions>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto" />
+                            <RowDefinition Height="Auto" />
+                        </Grid.RowDefinitions>
+
+                        <DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
+
+                            <TextBlock DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right"
+                                       Text="{Binding Path=NPackets, StringFormat='{}{0} packets'}"/>
+                            
+                            <TextBlock DockPanel.Dock="Left" TextWrapping="Wrap">
+                                <Run Text="{Binding JobNumber}" FontWeight="DemiBold" FontSize="12"/>
+                                <Run Text="{Binding JobName}" FontSize="12"/>
+                            </TextBlock>
+                        </DockPanel>
+
+                        <Border Grid.Column="1" Grid.Row="1" Padding="15">
+                            <Border BorderBrush="Black" BorderThickness="1"
+                                    Height="40">
+                                <Grid>
+                                    <Grid.ColumnDefinitions>
+                                        <ColumnDefinition Width="{Binding PercentRed, Converter={StaticResource PercentGridLengthConverter}}"/>
+                                        <ColumnDefinition Width="{Binding PercentYellow, Converter={StaticResource PercentGridLengthConverter}}"/>
+                                        <ColumnDefinition Width="{Binding PercentGreen, Converter={StaticResource PercentGridLengthConverter}}"/>
+                                    </Grid.ColumnDefinitions>
+
+                                    <Rectangle Grid.Column="0" Fill="Salmon">
+                                        <Rectangle.ToolTip>
+                                            <TextBlock Text="{Binding Path=PercentRed, StringFormat='{}{0:F2}% of packets are overdue.'}"/>
+                                        </Rectangle.ToolTip>
+                                    </Rectangle>
+                                    <Rectangle Grid.Column="1" Fill="LightYellow">
+                                        <Rectangle.ToolTip>
+                                            <TextBlock Text="{Binding Path=PercentYellow, StringFormat='{}{0:F2}% of packets are due in a week.'}"/>
+                                        </Rectangle.ToolTip>
+                                    </Rectangle>
+                                    <Rectangle Grid.Column="2" Fill="LightGreen">
+                                        <Rectangle.ToolTip>
+                                            <TextBlock Text="{Binding Path=PercentGreen, StringFormat='{}{0:F2}% of packets are due in more than a week.'}"/>
+                                        </Rectangle.ToolTip>
+                                    </Rectangle>
+                                </Grid>
+                            </Border>
+                        </Border>
+                        
+                        <Label Grid.Row="1" Grid.Column="0"
+                               FontSize="24" VerticalContentAlignment="Center"
+                               HorizontalContentAlignment="Center" Content="{Binding Path=TimeRequired}"
+                               Margin="0,0,0,5" FontWeight="DemiBold" />
+
+                        <Label Grid.Row="1" Grid.Column="2"
+                               FontSize="24" VerticalContentAlignment="Center"
+                               HorizontalContentAlignment="Center" Content="{Binding Path=NItems}"
+                               Margin="0,0,0,5" FontWeight="DemiBold" />
+                    </Grid>
+                </Border>
+
+                <!--Grid.ContextMenu>
+                    <ContextMenu x:Name="PacketMenu" Opened="PacketMenu_Opened" Tag="{Binding}">
+
+                        <MenuItem x:Name="ViewSetout" Header="View Manufacturing Packet" Click="ViewSetout_Click"
+                                  Tag="{Binding}" />
+                        <Separator x:Name="ViewSetoutSeparator" />
+
+                        <MenuItem x:Name="IssueSetout" Header="Issue Item" Click="IssueSetout_Click" Tag="{Binding}" />
+                        <MenuItem x:Name="ProgressSetout" Header="Progress Item" Click="ProgressSetout_Click"
+                                  Tag="{Binding}" />
+                        <MenuItem x:Name="SplitSetout" Header="Split Manufacturing" Click="SplitSetout_Click"
+                                  Tag="{Binding}" />
+                        <MenuItem x:Name="RevertSetout" Header="Revert Item" Click="RevertSetout_Click" Tag="{Binding}" />
+                        <Separator x:Name="ProgressSeparator" />
+
+                        <MenuItem x:Name="CancelItem" Header="Cancel Item" Click="CancelSetout_Click" Tag="{Binding}" />
+                        <Separator x:Name="CancelSeparator" />
+
+                        <MenuItem x:Name="CompeteItem" Header="Complete Item" Click="CompeteItem_Click" Tag="{Binding}" />
+                        <Separator x:Name="CompleteSeparator" />
+
+                        <MenuItem x:Name="ChangeTemplate" Header="Change Item Template" Tag="{Binding}" />
+                        <Separator x:Name="TemplateSeparator" />
+
+                        <MenuItem x:Name="ChangeDate" Header="Change Due Date" Tag="{Binding}" Click="ChangeDate_Click" />
+                        <Separator x:Name="ChangeDateSeparator" />
+
+                        <MenuItem x:Name="SetPriority" Header="Flag As Priority" Tag="{Binding}"
+                                  Click="SetPriority_Click" />
+                        <MenuItem x:Name="ClearPriority" Header="Clear Priority Flag" Tag="{Binding}"
+                                  Click="ClearPriority_Click" />
+                        <Separator x:Name="PrioritySeparator" />
+
+                        <MenuItem x:Name="EditIssues" Header="View/Update Issues" Tag="{Binding}"
+                                  Click="EditIssues_Click" />
+                        <MenuItem x:Name="SetHold" Header="Put Packet on Hold" Tag="{Binding}" Click="SetHold_Click" />
+                        <MenuItem x:Name="ClearHold" Header="Release Packet from Hold" Tag="{Binding}"
+                                  Click="ClearHold_Click" />
+                        <Separator x:Name="HoldSeparator" />
+
+                        <MenuItem x:Name="GenerateBarcodes" Header="Generate Barcodes" Click="GenerateBarcodes_Click"
+                                  Tag="{Binding}" />
+                        <MenuItem x:Name="ReprintBarcodes" Header="Re-print Barcodes" Click="ReprintBarcodes_Click"
+                                  Tag="{Binding}" />
+                        <MenuItem x:Name="RevokeBarcodes" Header="Revoke Barcodes" Click="RevokeBarcodes_Click"
+                                  Tag="{Binding}" />
+                        <Separator x:Name="BarcodeSeparator" />
+
+                        <MenuItem x:Name="ArchiveSetout" Header="Archive Item" Click="ArchiveSetout_Click"
+                                  Tag="{Binding}" />
+
+                    </ContextMenu>
+                </Grid.ContextMenu-->
+
+
+            </Grid>
+
+        </DataTemplate>
+
+        <!--DataTemplate x:Key="CompactManufacturingPanel">
+            <Border BorderBrush="Gray" BorderThickness="0.75" CornerRadius="5" Background="{Binding ColorKey}" Margin="0,1,0,1" Tag="{Binding}" MouseLeftButtonDown="Border_MouseLeftButtonDown">
+                <Grid Margin="5,2">
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="20"/>
+                        <ColumnDefinition Width="*" />
+                        <ColumnDefinition Width="Auto" />
+                    </Grid.ColumnDefinitions>
+                    <CheckBox Grid.Row="0" Grid.Column="0" Margin="0,1,5,0" IsChecked="{Binding Path=Checked}"  VerticalContentAlignment="Center" Tag="{Binding}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked"/>
+                    <Label Grid.Row="0" Grid.Column="1" Content="{Binding Path=Title}" Margin="0,0,5,0" VerticalContentAlignment="Center"/>
+                    <TextBlock Grid.Row="0" Grid.Column="2" Style="{StaticResource DueDateStyle}" TextAlignment="Center" VerticalAlignment="Center" Text="{Binding Path=DueDate, StringFormat='{}{0:dd MMM yy}'}" />
+
+                </Grid>
+
+                <Border.ContextMenu>
+                    <ContextMenu x:Name="PacketMenu" Opened="PacketMenu_Opened"  Tag="{Binding}">
+
+                        <MenuItem x:Name="ViewSetout" Header="View Manufacturing Packet" Click="ViewSetout_Click" Tag="{Binding}" />
+                        <Separator x:Name="ViewSetoutSeparator"/>
+
+                        <MenuItem x:Name="IssueSetout" Header="Issue Item" Click="IssueSetout_Click" Tag="{Binding}" />
+                        <MenuItem x:Name="ProgressSetout" Header="Progress Item" Click="ProgressSetout_Click" Tag="{Binding}" />
+                        <MenuItem x:Name="SplitSetout" Header="Split Manufacturing" Click="SplitSetout_Click"  Tag="{Binding}" />
+                        <MenuItem x:Name="RevertSetout" Header="Revert Item" Click="RevertSetout_Click"  Tag="{Binding}" />
+                        <Separator x:Name="ProgressSeparator"/>
+
+                        <MenuItem x:Name="CancelItem" Header="Cancel Item" Click="CancelSetout_Click"  Tag="{Binding}" />
+                        <Separator x:Name="CancelSeparator"/>
+
+                        <MenuItem x:Name="CompeteItem" Header="Complete Item" Click="CompeteItem_Click"  Tag="{Binding}" />
+                        <Separator x:Name="CompleteSeparator"/>
+
+                        <MenuItem x:Name="ChangeTemplate" Header="Change Item Template" Tag="{Binding}" />
+                        <Separator x:Name="TemplateSeparator"/>
+
+                        <MenuItem x:Name="ChangeDate" Header="Change Due Date" Tag="{Binding}" Click="ChangeDate_Click" />
+                        <Separator x:Name="ChangeDateSeparator"/>
+
+                        <MenuItem x:Name="SetPriority" Header="Flag As Priority" Tag="{Binding}" Click="SetPriority_Click" />
+                        <MenuItem x:Name="ClearPriority" Header="Clear Priority Flag" Tag="{Binding}" Click="ClearPriority_Click" />
+                        <Separator x:Name="PrioritySeparator"/>
+
+                        <MenuItem x:Name="SetHold" Header="Put Packet on Hold" Tag="{Binding}" Click="SetHold_Click" />
+                        <MenuItem x:Name="ClearHold" Header="Release Packet from Hold" Tag="{Binding}" Click="ClearHold_Click" />
+                        <Separator x:Name="HoldSeparator"/>
+
+                        <MenuItem x:Name="GenerateBarcodes" Header="Generate Barcodes" Click="GenerateBarcodes_Click"  Tag="{Binding}" />
+                        <MenuItem x:Name="ReprintBarcodes" Header="Re-print Barcodes" Click="ReprintBarcodes_Click"  Tag="{Binding}"/>
+                        <MenuItem x:Name="RevokeBarcodes" Header="Revoke Barcodes" Click="RevokeBarcodes_Click"  Tag="{Binding}" />
+                        <Separator x:Name="BarcodeSeparator" />
+
+                        <MenuItem x:Name="ArchiveSetout" Header="Archive Item" Click="ArchiveSetout_Click"  Tag="{Binding}" />
+
+                    </ContextMenu>
+                </Border.ContextMenu>
+
+            </Border>
+
+        </DataTemplate-->
+
+    </UserControl.Resources>
+    
+    <Grid x:Name="ColumnGrid">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="*" />
+            <ColumnDefinition Width="Auto" />
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="40" />
+            <RowDefinition Height="Auto" />
+            <RowDefinition Height="*" />
+        </Grid.RowDefinitions>
+
+        <Border Grid.Row="0" Grid.Column="0" BorderBrush="Gray" BorderThickness="0.75,0.75,0.75,0"
+                Background="WhiteSmoke">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*" />
+                    <ColumnDefinition Width="Auto" />
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+                <Label x:Name="ColumnTitle" Grid.Row="0" Grid.Column="0" VerticalContentAlignment="Center"
+                       HorizontalContentAlignment="Center" FontSize="18" Content="Column Title" />
+                <Label Grid.Row="0" Grid.Column="1" HorizontalContentAlignment="Center"
+                       VerticalContentAlignment="Center" FontSize="24" Content=" &#x276e; " Foreground="Gray"
+                       MouseLeftButtonUp="CollapseColumn_Click" />
+            </Grid>
+
+        </Border>
+
+        <Border Grid.Row="1" Grid.Column="0" Margin="0,0,0,2" BorderBrush="Gray" BorderThickness="0.75,0,0.75,0.75"
+                Background="WhiteSmoke">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*" />
+                    <ColumnDefinition Width="Auto" />
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+                <Label x:Name="ItemCount" Grid.Column="0" HorizontalContentAlignment="Left"
+                       VerticalContentAlignment="Center" Content="# Jobs" />
+                <Label x:Name="TimeRequired" Grid.Column="1" Grid.ColumnSpan="2" HorizontalContentAlignment="Right"
+                       VerticalContentAlignment="Center" Content="Time Required: (calculating)" />
+            </Grid>
+        </Border>
+
+        <ListBox x:Name="Items" Grid.Row="2" Grid.Column="0" VirtualizingPanel.IsVirtualizing="True"
+                 VirtualizingPanel.VirtualizationMode="Recycling" HorizontalContentAlignment="Stretch"
+                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"
+                 ItemTemplate="{StaticResource FullManufacturingPanel}">
+            <ListBox.Resources>
+                <Style TargetType="{x:Type ListBoxItem}">
+                    <Style.Triggers>
+                        <Trigger Property="IsSelected" Value="True">
+                            <Setter Property="Foreground" Value="Black" />
+                        </Trigger>
+                    </Style.Triggers>
+                </Style>
+            </ListBox.Resources>
+        </ListBox>
+
+        <Border Grid.Row="0" Grid.Column="1" BorderBrush="Gray" BorderThickness="0.75" Background="WhiteSmoke">
+            <Label FontSize="24" Content=" &#x276f; " Foreground="Gray" HorizontalContentAlignment="Center"
+                   VerticalContentAlignment="Center" MouseLeftButtonUp="ExpandColumn_Click" />
+        </Border>
+
+        <Border Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Margin="0,2,0,0" BorderBrush="Gray"
+                BorderThickness="0.75" Background="WhiteSmoke">
+            <DockPanel>
+                <Label x:Name="HiddenCount" DockPanel.Dock="Top" FontSize="18" Content=" [HiddenCount]">
+                    <Label.LayoutTransform>
+                        <RotateTransform Angle="270" />
+                    </Label.LayoutTransform>
+                </Label>
+                <Label x:Name="HiddenTitle" DockPanel.Dock="Top" FontSize="18" Content="Hidden Title"
+                       HorizontalContentAlignment="Right">
+                    <Label.LayoutTransform>
+                        <RotateTransform Angle="270" />
+                    </Label.LayoutTransform>
+                </Label>
+            </DockPanel>
+
+        </Border>
+
+
+    </Grid>
+</UserControl>

+ 190 - 0
prs.desktop/Panels/Manufacturing/ManufacturingPanelJobColumn.xaml.cs

@@ -0,0 +1,190 @@
+using Comal.Classes;
+using InABox.Core;
+using Syncfusion.Windows.Controls.Grid;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PRSDesktop
+{
+    [ValueConversion(typeof(double), typeof(GridLength))]
+    public class PercentGridLengthConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            if (value is not double d) return new GridLength(0);
+
+            return new GridLength(d, GridUnitType.Star);
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public class ManufacturingJobModel
+    {
+        public string JobNumber { get; set; }
+
+        public string JobName { get; set; }
+
+        public Guid JobID { get; set; }
+
+        public int NHours { get; set; }
+        public int NMinutes { get; set; }
+
+        public string TimeRequired => $"{NHours}:{NMinutes:D2}";
+
+        public int NPackets { get; set; }
+
+        public int NItems { get; set; }
+
+        public double PercentGreen { get; set; }
+        public double PercentYellow { get; set; }
+        public double PercentRed { get; set; }
+
+        public ManufacturingJobModel(Guid jobID, string jobNumber, string jobName)
+        {
+            JobID = jobID;
+            JobNumber = jobNumber;
+            JobName = jobName;
+        }
+    }
+
+    /// <summary>
+    /// Interaction logic for ManufacturingPanelJobColumn.xaml
+    /// </summary>
+    public partial class ManufacturingPanelJobColumn : UserControl, IManufacturingPanelColumn
+    {
+        public string Title
+        {
+            get => (string)ColumnTitle.Content;
+            set
+            {
+                ColumnTitle.Content = value;
+                HiddenTitle.Content = value;
+            }
+        }
+
+        private bool _collapsed;
+        public bool Collapsed
+        {
+            get => _collapsed;
+            set
+            {
+                _collapsed = value;
+                ColumnGrid.ColumnDefinitions[0].Width = value ? new GridLength(0, GridUnitType.Pixel) : new GridLength(1, GridUnitType.Star);
+                ColumnGrid.ColumnDefinitions[1].Width = value ? new GridLength(1, GridUnitType.Auto) : new GridLength(0, GridUnitType.Pixel);
+                MinWidth = _collapsed ? 35.0F : 300.0F;
+                OnCollapsed?.Invoke(this, value);
+            }
+        }
+        public Guid Category { get; set; }
+
+        public event CollapsingEventHandler? OnCollapsed;
+
+        public ManufacturingPanelData Data { get; set; }
+
+        private List<ManufacturingPacket> Packets = new();
+
+        public delegate void SelectJobEvent(Guid jobID);
+
+        public event SelectJobEvent? OnSelectJob;
+
+        public ManufacturingPanelJobColumn()
+        {
+            InitializeComponent();
+        }
+
+        public void SetPackets(IEnumerable<ManufacturingPacket> packets)
+        {
+            Packets = packets.ToList();
+
+            var models = new List<ManufacturingJobModel>();
+            foreach(var jobPackets in Packets.GroupBy(x => x.SetoutLink.JobLink.ID).OrderBy(x => x.Key))
+            {
+                var jobrow = Data.Jobs.Rows.FirstOrDefault(r => r.Get<Job, Guid>(c => c.ID).Equals(jobPackets.Key));
+                if (jobrow is null) continue;
+
+                var model = new ManufacturingJobModel(
+                    jobrow.Get<Job, Guid>(x => x.ID),
+                    jobrow.Get<Job, string>(x => x.JobNumber),
+                    jobrow.Get<Job, string>(x => x.Name));
+
+                int nGreen = 0;
+                int nYellow = 0;
+                int nRed = 0;
+                double nHours = 0;
+                foreach(var packet in jobPackets)
+                {
+                    nHours += packet.StageLink.Time.TotalHours * (1 - packet.StageLink.PercentageComplete / 100.0f);
+
+                    ++model.NPackets;
+                    model.NItems += packet.Quantity;
+
+                    var dueDate = packet.DueDate.IsEmpty() ? DateTime.Today : packet.DueDate;
+                    var estDate = packet.EstimatedDate.IsEmpty() ? DateTime.Today : packet.EstimatedDate;
+
+                    if (dueDate < estDate)
+                        ++nRed;
+                    else if (dueDate < estDate.AddDays(7))
+                        ++nYellow;
+                    else
+                        ++nGreen;
+                }
+
+                model.NHours = (int)Math.Floor(nHours);
+                model.NMinutes = (int)((nHours - model.NHours) * 60);
+
+                model.PercentGreen = (double)nGreen / model.NPackets * 100.0;
+                model.PercentYellow = (double)nYellow / model.NPackets * 100.0;
+                model.PercentRed = (double)nRed / model.NPackets * 100.0;
+
+                models.Add(model);
+            }
+            Collapsed = models.Count == 0;
+            ItemCount.Content = string.Format("{0} Jobs", models.Count);
+            HiddenCount.Content = string.Format(" ({0} Jobs)", models.Count);
+            TimeRequired.Content = string.Format("Time Required: {0:F2} hrs", models.Sum(x => x.NHours + x.NMinutes / 60.0));
+
+            Items.ItemsSource = models;
+        }
+
+        public IEnumerable<ManufacturingPacket> GetSelectedPackets() => Packets;
+
+        public IEnumerable<ManufacturingPacket> GetPackets() => Packets;
+
+        private void CollapseColumn_Click(object sender, MouseButtonEventArgs e)
+        {
+            Collapsed = true;
+        }
+
+        private void ExpandColumn_Click(object sender, MouseButtonEventArgs e)
+        {
+            Collapsed = false;
+        }
+
+        private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            if (sender is not FrameworkElement el || el.Tag is not Guid jobID)
+                return;
+            if(e.ClickCount == 2)
+            {
+                OnSelectJob?.Invoke(jobID);
+            }
+        }
+    }
+}