JobManufacturingSummary.xaml.cs 20 KB

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