JobManufacturingSummary.xaml.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.WPF;
  5. using javax.smartcardio;
  6. using NPOI.SS.Formula.Functions;
  7. using PRSDesktop.WidgetGroups;
  8. using Syncfusion.UI.Xaml.Grid;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.ComponentModel;
  12. using System.Data;
  13. using System.Linq;
  14. using System.Runtime.CompilerServices;
  15. using System.Text;
  16. using System.Threading.Tasks;
  17. using System.Windows;
  18. using System.Windows.Controls;
  19. using System.Windows.Data;
  20. using System.Windows.Documents;
  21. using System.Windows.Input;
  22. using System.Windows.Media;
  23. using System.Windows.Media.Imaging;
  24. using System.Windows.Navigation;
  25. using System.Windows.Shapes;
  26. using InABox.Configuration;
  27. using DataColumn = System.Data.DataColumn;
  28. using DataRow = System.Data.DataRow;
  29. using Columns = InABox.Core.Columns;
  30. namespace PRSDesktop.Dashboards.Manufacturing
  31. {
  32. public class HeaderModel
  33. {
  34. public string Name { get; set; }
  35. public HeaderModel(string name)
  36. {
  37. Name = name;
  38. }
  39. }
  40. public class JobModel
  41. {
  42. public string JobNumber { get; set; }
  43. public string Name { get; set; }
  44. public Guid ID { get; set; }
  45. public double NHours { get; set; }
  46. public int NPackets { get; set; }
  47. public Visibility HoursVisibility;
  48. public JobModel(string jobNumber, string name, Guid ID, double nHours, int nPackets)
  49. {
  50. JobNumber = jobNumber;
  51. Name = name;
  52. this.ID = ID;
  53. NHours = nHours;
  54. NPackets = nPackets;
  55. }
  56. }
  57. public class CardModel
  58. {
  59. private bool _empty;
  60. public int NPackets { get; set; }
  61. public double NHours { get; set; }
  62. public bool Empty
  63. {
  64. get => _empty || NPackets == 0;
  65. set => _empty = value;
  66. }
  67. public Visibility HoursVisibility;
  68. public CardModel(int nPackets, int nHours)
  69. {
  70. NPackets = nPackets;
  71. NHours = nHours;
  72. Empty = false;
  73. }
  74. public CardModel()
  75. {
  76. NPackets = 0;
  77. NHours = 0.0;
  78. Empty = true;
  79. }
  80. }
  81. public class JobManufacturingSummaryProperties : IUserConfigurationSettings, IDashboardProperties
  82. {
  83. public Guid JobStatus;
  84. public bool ShowEmptyJobs;
  85. public bool IncludeTimeRemaining;
  86. }
  87. public class JobManufacturingSummaryElement : DashboardElement<JobManufacturingSummary, WidgetGroups.Manufacturing, JobManufacturingSummaryProperties>
  88. {
  89. }
  90. /// <summary>
  91. /// Interaction logic for JobManufacturingSummary.xaml
  92. /// </summary>
  93. public partial class JobManufacturingSummary : UserControl, IDashboardWidget<WidgetGroups.Manufacturing, JobManufacturingSummaryProperties>,
  94. IHeaderDashboard,
  95. INotifyPropertyChanged
  96. {
  97. private class SectionColumn
  98. {
  99. public string ColumnName { get; set; }
  100. public string DisplayName { get; set; }
  101. public SolidColorBrush Background { get; set; }
  102. }
  103. private class ManufacturingTotal
  104. {
  105. public int NPackets { get; set; } = 0;
  106. public double NHours { get; set; } = 0;
  107. }
  108. private readonly Dictionary<Guid, SectionColumn> SectionColumns = new();
  109. private readonly Dictionary<Guid, SectionColumn> FactoryColumns = new();
  110. public event PropertyChangedEventHandler? PropertyChanged;
  111. private List<Job> _jobs = new();
  112. private List<Job> Jobs
  113. {
  114. get => _jobs;
  115. set
  116. {
  117. _jobs = value;
  118. OnPropertyChanged(nameof(NumberOfJobs));
  119. }
  120. }
  121. private List<ManufacturingFactory> Factories { get; set; } = new();
  122. private Dictionary<Guid, List<ManufacturingSection>> Sections { get; set; } = new();
  123. private List<FrameworkElement> Cards { get; set; } = new();
  124. private DataTable Data { get; set; }
  125. private Dictionary<Guid, StackedColumn> FactoryColumnHeaders = new();
  126. public int NumberOfJobs => Jobs.Count;
  127. public JobManufacturingSummary()
  128. {
  129. InitializeComponent();
  130. }
  131. public JobManufacturingSummaryProperties Properties { get; set; }
  132. public event LoadSettings<JobManufacturingSummaryProperties>? LoadSettings;
  133. public event SaveSettings<JobManufacturingSummaryProperties>? SaveSettings;
  134. public DashboardHeader Header { get; } = new();
  135. public void Setup()
  136. {
  137. Jobs = new Client<Job>()
  138. .Query(
  139. new Filter<Job>().None(),
  140. Columns.None<Job>().Add(x => x.ID)
  141. .Add(x => x.JobNumber)
  142. .Add(x => x.Name)
  143. .Add(x => x.Color))
  144. .ToObjects<Job>().ToList();
  145. Factories = new Client<ManufacturingFactory>()
  146. .Query(null, Columns.None<ManufacturingFactory>().Add(x => x.ID).Add(x => x.Name), new SortOrder<ManufacturingFactory>(x => x.Sequence))
  147. .ToObjects<ManufacturingFactory>().ToList();
  148. Sections = new Client<ManufacturingSection>()
  149. .Query(
  150. new Filter<ManufacturingSection>(x => x.Hidden).IsEqualTo(false),
  151. Columns.None<ManufacturingSection>().Add(x => x.ID)
  152. .Add(x => x.Factory.ID)
  153. .Add(x => x.Name),
  154. new SortOrder<ManufacturingSection>(x => x.Sequence))
  155. .ToObjects<ManufacturingSection>()
  156. .GroupBy(x => x.Factory.ID).ToDictionary(x => x.Key, x => x.ToList());
  157. SetupHeader();
  158. SetupGrid();
  159. RefreshJobs();
  160. }
  161. #region Header
  162. private ComboBox JobStatusBox;
  163. private JobStatus? SelectedStatus => JobStatusBox.SelectedValue as JobStatus;
  164. private void SetupHeader()
  165. {
  166. JobStatusBox = new ComboBox
  167. {
  168. Padding = new Thickness(5)
  169. };
  170. var statuses = new Client<JobStatus>().Load();
  171. JobStatusBox.ItemsSource = statuses.Select(x => new Tuple<string, JobStatus>($"{x.Code}: {x.Description}", x));
  172. JobStatusBox.DisplayMemberPath = "Item1";
  173. JobStatusBox.SelectedValuePath = "Item2";
  174. JobStatusBox.SelectedValue = (Properties.JobStatus != Guid.Empty
  175. ? statuses.FirstOrDefault(x => x.ID == Properties.JobStatus)
  176. : null) ?? statuses.FirstOrDefault(x => x.Default) ?? statuses.FirstOrDefault();
  177. JobStatusBox.SelectionChanged += StatusBox_SelectionChanged;
  178. var showEmptyJobsLabel = new Label { Content = "Show Empty Jobs?" };
  179. var emptyJobsCheckBox = new CheckBox
  180. {
  181. IsChecked = Properties.ShowEmptyJobs,
  182. VerticalAlignment = VerticalAlignment.Center
  183. };
  184. emptyJobsCheckBox.Checked += EmptyJobsCheckBox_Checked;
  185. emptyJobsCheckBox.Unchecked += EmptyJobsCheckBox_Checked;
  186. var includeRemainingLabel = new Label { Content = "Include Remaining Time?" };
  187. var includeRemainingCheckBox = new CheckBox
  188. {
  189. IsChecked = Properties.IncludeTimeRemaining,
  190. VerticalAlignment = VerticalAlignment.Center
  191. };
  192. includeRemainingCheckBox.Checked += IncludeRemainingCheckBox_Checked;
  193. includeRemainingCheckBox.Unchecked += IncludeRemainingCheckBox_Checked;
  194. Header.Add(JobStatusBox)
  195. .Add(showEmptyJobsLabel)
  196. .Add(emptyJobsCheckBox)
  197. .Add(includeRemainingLabel)
  198. .Add(includeRemainingCheckBox);
  199. }
  200. private void IncludeRemainingCheckBox_Checked(object sender, RoutedEventArgs e)
  201. {
  202. if (sender is not CheckBox checkBox) return;
  203. Properties.IncludeTimeRemaining = checkBox.IsChecked == true;
  204. Refresh();
  205. }
  206. private void EmptyJobsCheckBox_Checked(object sender, RoutedEventArgs e)
  207. {
  208. if (sender is not CheckBox checkBox) return;
  209. Properties.ShowEmptyJobs = checkBox.IsChecked == true;
  210. Refresh();
  211. }
  212. private void RefreshJobs()
  213. {
  214. Jobs = new Client<Job>()
  215. .Query(
  216. new Filter<Job>(x => x.JobStatus.ID).IsEqualTo(Properties.JobStatus),
  217. Columns.None<Job>().Add(x => x.ID)
  218. .Add(x => x.JobNumber)
  219. .Add(x => x.Name)
  220. .Add(x => x.Color))
  221. .ToObjects<Job>().ToList();
  222. Refresh();
  223. }
  224. private void StatusBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
  225. {
  226. Properties.JobStatus = SelectedStatus?.ID ?? Guid.Empty;
  227. RefreshJobs();
  228. }
  229. #endregion
  230. private void SetupGrid()
  231. {
  232. DataGrid.HeaderTemplate = Resources["HeaderTemplate"] as DataTemplate;
  233. Data = new DataTable();
  234. Data.Columns.Add("Job", typeof(object));
  235. var stackedHeaderRow = new StackedHeaderRow();
  236. var stackedDetailRow = new StackedHeaderRow();
  237. var factoryIdx = 0;
  238. foreach (var factory in Factories)
  239. {
  240. var columns = new List<string>();
  241. var toBeIssued = factory.ID.ToString();
  242. Data.Columns.Add(toBeIssued, typeof(object));
  243. columns.Add(toBeIssued);
  244. FactoryColumns.Add(factory.ID, new SectionColumn
  245. {
  246. ColumnName = toBeIssued,
  247. DisplayName = "To Be Issued",
  248. Background = factoryIdx % 2 == 0 ? new SolidColorBrush(Colors.WhiteSmoke) : new SolidColorBrush(Colors.LightGray)
  249. });
  250. if (Sections.TryGetValue(factory.ID, out var sections))
  251. {
  252. foreach (var section in sections)
  253. {
  254. var columnName = section.ID.ToString();
  255. SectionColumns.Add(section.ID, new SectionColumn
  256. {
  257. ColumnName = columnName,
  258. DisplayName = section.Name,
  259. Background = factoryIdx % 2 == 0 ? new SolidColorBrush(Colors.WhiteSmoke) : new SolidColorBrush(Colors.LightGray)
  260. });
  261. Data.Columns.Add(columnName, typeof(object));
  262. columns.Add(columnName);
  263. }
  264. }
  265. var column = new StackedColumn
  266. {
  267. ChildColumns = string.Join(',', columns),
  268. HeaderText = ""
  269. };
  270. FactoryColumnHeaders.Add(factory.ID, column);
  271. stackedDetailRow.StackedColumns.Add(column);
  272. stackedHeaderRow.StackedColumns.Add(new StackedColumn
  273. {
  274. ChildColumns = string.Join(',', columns),
  275. HeaderText = factory.Name
  276. });
  277. ++factoryIdx;
  278. }
  279. DataGrid.StackedHeaderRows.Add(stackedHeaderRow);
  280. DataGrid.StackedHeaderRows.Add(stackedDetailRow);
  281. }
  282. private void SetPosition(FrameworkElement element, int row, int rowSpan, int column, int columnSpan)
  283. {
  284. element.SetValue(Grid.RowProperty, row);
  285. element.SetValue(Grid.RowSpanProperty, rowSpan);
  286. element.SetValue(Grid.ColumnProperty, column);
  287. element.SetValue(Grid.ColumnSpanProperty, columnSpan);
  288. }
  289. private void LoadPackets(
  290. IEnumerable<IGrouping<Guid, ManufacturingPacket>> packets,
  291. Dictionary<Guid, SectionColumn> columns,
  292. Dictionary<Guid, DataRow> jobRows)
  293. {
  294. var groupPackets = packets
  295. .ToDictionary(
  296. x => x.Key,
  297. x => x.GroupBy(x => x.SetoutLink.JobLink.ID)
  298. .ToDictionary(x => x.Key, x => x.Aggregate(new CardModel(0, 0)
  299. {
  300. HoursVisibility = Properties.IncludeTimeRemaining ? Visibility.Visible : Visibility.Collapsed
  301. }, (c, p) =>
  302. {
  303. c.NHours += p.TimeRemaining.TotalHours;
  304. ++c.NPackets;
  305. return c;
  306. })));
  307. foreach (var (id, column) in columns)
  308. {
  309. var packetList = groupPackets.GetValueOrDefault(id);
  310. foreach (var job in Jobs)
  311. {
  312. if (!jobRows.TryGetValue(job.ID, out var row))
  313. {
  314. continue;
  315. }
  316. if (packetList is null || !packetList.TryGetValue(job.ID, out var model))
  317. {
  318. model = new CardModel();
  319. }
  320. row[column.ColumnName] = model;
  321. }
  322. }
  323. }
  324. public void Refresh()
  325. {
  326. using var profiler = new Profiler(true);
  327. Data.Rows.Clear();
  328. var rows = new Dictionary<Guid, DataRow>();
  329. foreach (var job in Jobs)
  330. {
  331. var row = Data.NewRow();
  332. Data.Rows.Add(row);
  333. row["Job"] = job;
  334. rows[job.ID] = row;
  335. }
  336. var sectionColumns = Columns.None<ManufacturingPacket>().Add(x => x.ID)
  337. .Add(x => x.SetoutLink.JobLink.ID)
  338. .Add(x => x.StageLink.SectionID);
  339. var factoryColumns = Columns.None<ManufacturingPacket>().Add(x => x.ID)
  340. .Add(x => x.SetoutLink.JobLink.ID)
  341. .Add(x => x.ManufacturingTemplateLink.Factory.ID);
  342. if (Properties.IncludeTimeRemaining)
  343. {
  344. sectionColumns.Add(x => x.TimeRemaining);
  345. factoryColumns.Add(x => x.TimeRemaining);
  346. }
  347. profiler.Log("Setup");
  348. LoadPackets(
  349. new Client<ManufacturingPacket>()
  350. .Query(
  351. new Filter<ManufacturingPacket>(x => x.Archived).IsEqualTo(DateTime.MinValue)
  352. .And(x => x.StageLink.SectionID).IsNotEqualTo(Guid.Empty)
  353. .And(x => x.Completed).IsEqualTo(DateTime.MinValue)
  354. .And(x => x.SetoutLink.JobLink.ID).InList(Jobs.Select(x => x.ID).ToArray()),
  355. sectionColumns)
  356. .ToObjects<ManufacturingPacket>()
  357. .GroupBy(x => x.StageLink.SectionID),
  358. SectionColumns,
  359. rows);
  360. profiler.Log("Section");
  361. LoadPackets(
  362. new Client<ManufacturingPacket>()
  363. .Query(
  364. new Filter<ManufacturingPacket>(x => x.Archived).IsEqualTo(DateTime.MinValue)
  365. .And(x => x.StageLink.SectionID).IsEqualTo(Guid.Empty)
  366. .And(x => x.Completed).IsEqualTo(DateTime.MinValue)
  367. .And(x => x.SetoutLink.JobLink.ID).InList(Jobs.Select(x => x.ID).ToArray()),
  368. factoryColumns)
  369. .ToObjects<ManufacturingPacket>()
  370. .GroupBy(x => x.ManufacturingTemplateLink.Factory.ID),
  371. FactoryColumns,
  372. rows);
  373. profiler.Log("Factory");
  374. var removes = new List<Guid>();
  375. foreach(var (jobID, row) in rows)
  376. {
  377. var any = false;
  378. var nHours = 0.0;
  379. var nPackets = 0;
  380. foreach (var (id, column) in FactoryColumns.Concat(SectionColumns))
  381. {
  382. if (row[column.ColumnName] is CardModel model && !model.Empty)
  383. {
  384. any = true;
  385. nHours += model.NHours;
  386. nPackets += model.NPackets;
  387. }
  388. }
  389. if (!any)
  390. {
  391. removes.Add(jobID);
  392. }
  393. else
  394. {
  395. if(row["Job"] is Job job)
  396. {
  397. row["Job"] = new JobModel(job.JobNumber, job.Name, job.ID, nHours, nPackets)
  398. {
  399. HoursVisibility = Properties.IncludeTimeRemaining ? Visibility.Visible : Visibility.Collapsed,
  400. };
  401. }
  402. }
  403. }
  404. if (!Properties.ShowEmptyJobs)
  405. {
  406. foreach(var remove in removes)
  407. {
  408. if(rows.Remove(remove, out var row))
  409. {
  410. Data.Rows.Remove(row);
  411. }
  412. }
  413. }
  414. var totals = new Dictionary<Guid, ManufacturingTotal>();
  415. foreach(var column in Data.Columns)
  416. {
  417. if (column is not DataColumn dataColumn) continue;
  418. Guid factoryID;
  419. var sectionColumn = SectionColumns.FirstOrDefault(x => x.Value.ColumnName == dataColumn.ColumnName);
  420. if(sectionColumn.Key != Guid.Empty)
  421. {
  422. factoryID = Sections.FirstOrDefault(x => x.Value.Any(x => x.ID == sectionColumn.Key)).Key;
  423. }
  424. else
  425. {
  426. var factoryColumn = FactoryColumns.FirstOrDefault(x => x.Value.ColumnName == dataColumn.ColumnName);
  427. factoryID = factoryColumn.Key;
  428. }
  429. if (factoryID == Guid.Empty) continue;
  430. if(!totals.TryGetValue(factoryID, out var total))
  431. {
  432. total = new();
  433. totals[factoryID] = total;
  434. }
  435. foreach(var row in rows.Values)
  436. {
  437. if (row[dataColumn] is not CardModel model) continue;
  438. total.NHours += model.NHours;
  439. total.NPackets += model.NPackets;
  440. }
  441. }
  442. foreach(var (factoryID, total) in totals)
  443. {
  444. var header = FactoryColumnHeaders[factoryID];
  445. if (Properties.IncludeTimeRemaining)
  446. {
  447. header.HeaderText = $"Packets: {total.NPackets}, Hours: {total.NHours:f2}";
  448. }
  449. else
  450. {
  451. header.HeaderText = $"Packets: {total.NPackets}";
  452. }
  453. }
  454. DataGrid.ItemsSource = Data;
  455. }
  456. public void Shutdown(CancelEventArgs? cancel)
  457. {
  458. }
  459. private void DataGrid_AutoGeneratingColumn(object sender, Syncfusion.UI.Xaml.Grid.AutoGeneratingColumnArgs e)
  460. {
  461. if(Guid.TryParse(e.Column.MappingName, out var id))
  462. {
  463. if(SectionColumns.TryGetValue(id, out var column) || FactoryColumns.TryGetValue(id, out column))
  464. {
  465. var style = new Style();
  466. style.Setters.Add(new Setter(GridCell.BackgroundProperty, column.Background));
  467. e.Column = new GridTemplateColumn
  468. {
  469. HeaderText = column.DisplayName,
  470. CellStyle = style,
  471. MappingName = e.Column.MappingName,
  472. CellTemplate = Resources["CardTemplate"] as DataTemplate,
  473. SetCellBoundValue = true,
  474. ColumnSizer = GridLengthUnitType.Auto
  475. };
  476. }
  477. }
  478. else
  479. {
  480. e.Column = new GridTemplateColumn
  481. {
  482. CellTemplate = Resources["JobHeaderTemplate"] as DataTemplate,
  483. HeaderText = e.Column.HeaderText,
  484. MappingName = e.Column.MappingName,
  485. SetCellBoundValue = true,
  486. ColumnSizer = GridLengthUnitType.Auto
  487. };
  488. }
  489. }
  490. private void DataGrid_QueryRowHeight(object sender, QueryRowHeightEventArgs e)
  491. {
  492. if(DataGrid.GridColumnSizer.GetAutoRowHeight(e.RowIndex, new GridRowSizingOptions(), out var height))
  493. {
  494. e.Height = height;
  495. e.Handled = true;
  496. }
  497. }
  498. private void OnPropertyChanged([CallerMemberName] string name = "")
  499. {
  500. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
  501. }
  502. }
  503. }