ReservationManagementItemGrid.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Configuration;
  4. using InABox.Core;
  5. using InABox.DynamicGrid;
  6. using InABox.Wpf;
  7. using InABox.WPF;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Drawing;
  11. using System.Linq;
  12. using System.Linq.Expressions;
  13. using System.Windows;
  14. using System.Windows.Controls;
  15. using System.Windows.Media;
  16. using Brush = System.Windows.Media.Brush;
  17. namespace PRSDesktop;
  18. public delegate void JobRequiItemSelect(CoreRow[] rows);
  19. public delegate void GridRefresh();
  20. public class ReservationManagementItemGrid : DynamicDataGrid<JobRequisitionItem>
  21. {
  22. private readonly ReservationManagementUserSettings _userSettings = new ReservationManagementUserSettings();
  23. private Button ArchiveButton;
  24. public bool ShowColors { get; set; }
  25. public int DueDateAlert { get; set; }
  26. public int DueDateWarning { get; set; }
  27. protected override bool ShowSequenceButtons => false;
  28. private class UIComponent : DynamicGridGridUIComponent<JobRequisitionItem>
  29. {
  30. private ReservationManagementItemGrid Grid;
  31. public UIComponent(ReservationManagementItemGrid grid)
  32. {
  33. Grid = grid;
  34. Parent = grid;
  35. }
  36. protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
  37. {
  38. if (!Grid.ShowColors) return null;
  39. if (column is DynamicGridColumn col)
  40. {
  41. if (String.Equals(col.ColumnName,
  42. CoreUtils.GetFullPropertyName<JobRequisitionItem, DateTime>(x => x.Requisition.DueDate, ".")))
  43. {
  44. var due = row.Get<JobRequisitionItem, DateTime>(x => x.Requisition.DueDate);
  45. if (!due.IsEmpty())
  46. {
  47. var background = DateTime.Today > due.Date
  48. ? Colors.LightSalmon
  49. : DateTime.Today.AddDays(Grid.DueDateWarning) >= due.Date
  50. ? Colors.Orange
  51. : DateTime.Today.AddDays(Grid.DueDateAlert) >= due.Date
  52. ? Colors.LightYellow
  53. : Colors.LightGreen;
  54. return new SolidColorBrush(background) { Opacity = 0.5 };
  55. }
  56. }
  57. }
  58. else if(column is DynamicActionColumn dac)
  59. {
  60. if (dac == Grid.InStockColumn
  61. || dac == Grid.OnOrderColumn)
  62. {
  63. return Colors.LightBlue.ToBrush(0.5);
  64. }
  65. else if (dac == Grid.TreatmentRequiredColumn
  66. || dac == Grid.TreatmentOnOrderColumn)
  67. {
  68. return Colors.Plum.ToBrush(0.5);
  69. }
  70. }
  71. var qty = row.Get<JobRequisitionItem, double>(x => x.Qty);
  72. if(row.Get<JobRequisitionItem, double>(x => x.Issued) >= qty)
  73. {
  74. return Colors.Silver.ToBrush(0.5);
  75. }
  76. else if(row.Get<JobRequisitionItem, double>(x => x.Allocated) + row.Get<JobRequisitionItem, double>(x => x.Issued) >= qty)
  77. {
  78. return Colors.LightGreen.ToBrush(0.5);
  79. }
  80. else if(row.Get<JobRequisitionItem, double>(x => x.InStock) + row.Get<JobRequisitionItem, double>(x => x.Issued) >= qty)
  81. {
  82. return Colors.Orange.ToBrush(0.5);
  83. }
  84. else
  85. {
  86. return Colors.LightSalmon.ToBrush(0.5);
  87. }
  88. }
  89. }
  90. private DynamicActionColumn InStockColumn;
  91. private DynamicActionColumn OnOrderColumn;
  92. private DynamicActionColumn TreatmentRequiredColumn;
  93. private DynamicActionColumn TreatmentOnOrderColumn;
  94. private DynamicActionColumn AllocatedColumn;
  95. private DynamicActionColumn IssuedColumn;
  96. public ReservationManagementItemGrid()
  97. {
  98. _userSettings = new UserConfiguration<ReservationManagementUserSettings>().Load();
  99. FilterComponent.SetSettings(_userSettings.Filters, false);
  100. HiddenColumns.Add(x => x.ID);
  101. HiddenColumns.Add(x => x.Qty);
  102. HiddenColumns.Add(x => x.InStock);
  103. HiddenColumns.Add(x => x.OnOrder);
  104. HiddenColumns.Add(x => x.TreatmentOnOrder);
  105. HiddenColumns.Add(x => x.TreatmentRequired);
  106. HiddenColumns.Add(x => x.Allocated);
  107. HiddenColumns.Add(x => x.Issued);
  108. HiddenColumns.Add(x => x.Product.ID);
  109. HiddenColumns.Add(x => x.Product.Code);
  110. HiddenColumns.Add(x => x.Product.Group.ID);
  111. HiddenColumns.Add(x => x.Product.Group.Code);
  112. HiddenColumns.Add(x => x.Product.Group.Description);
  113. HiddenColumns.Add(x => x.Style.ID);
  114. HiddenColumns.Add(x => x.Style.Code);
  115. HiddenColumns.Add(x => x.Style.Description);
  116. HiddenColumns.Add(x => x.Status);
  117. HiddenColumns.Add(x => x.Requisition.ID);
  118. HiddenColumns.Add(x => x.Requisition.Job.ID);
  119. HiddenColumns.Add(x => x.Requisition.Job.JobNumber);
  120. HiddenColumns.Add(x => x.Requisition.Job.Name);
  121. HiddenColumns.Add(x => x.Requisition.Number);
  122. HiddenColumns.Add(x => x.Requisition.DueDate);
  123. HiddenColumns.Add(x => x.Job.ID);
  124. HiddenColumns.Add(x => x.Job.Name);
  125. HiddenColumns.Add(x => x.Job.JobNumber);
  126. HiddenColumns.Add(x => x.Dimensions.UnitSize);
  127. HiddenColumns.Add(x => x.Dimensions.Length);
  128. HiddenColumns.Add(x => x.Dimensions.Width);
  129. HiddenColumns.Add(x => x.Dimensions.Height);
  130. HiddenColumns.Add(x => x.Dimensions.Weight);
  131. HiddenColumns.Add(x => x.Dimensions.Quantity);
  132. HiddenColumns.Add(x => x.Dimensions.Value);
  133. HiddenColumns.Add(x => x.Dimensions.Unit.ID);
  134. HiddenColumns.Add(x => x.Dimensions.Unit.HasLength);
  135. HiddenColumns.Add(x => x.Dimensions.Unit.HasHeight);
  136. HiddenColumns.Add(x => x.Dimensions.Unit.HasWidth);
  137. HiddenColumns.Add(x => x.Dimensions.Unit.HasWeight);
  138. HiddenColumns.Add(x => x.Dimensions.Unit.HasQuantity);
  139. HiddenColumns.Add(x => x.Dimensions.Unit.Formula);
  140. HiddenColumns.Add(x => x.Dimensions.Unit.Format);
  141. HiddenColumns.Add(x => x.Dimensions.Unit.Code);
  142. HiddenColumns.Add(x => x.Dimensions.Unit.Description);
  143. AddDoubleColumn(x => x.Qty, "Qty.");
  144. InStockColumn = AddDoubleColumn(x => x.InStock, "Stk.");
  145. OnOrderColumn = AddDoubleColumn(x => x.OnOrder, "Ord.");
  146. TreatmentRequiredColumn = AddDoubleColumn(x => x.TreatmentRequired, "Req.");
  147. TreatmentOnOrderColumn = AddDoubleColumn(x => x.TreatmentOnOrder, "Ord.");
  148. AllocatedColumn = AddDoubleColumn(x => x.Allocated, "Stk.");
  149. IssuedColumn = AddDoubleColumn(x => x.Issued, "Iss.");
  150. if (Security.CanEdit<JobRequisitionItem>())
  151. ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
  152. ColumnsTag = "JobRequisitionReview";
  153. FilterComponent.OnFiltersSelected += GridOnFilterSelected;
  154. ArchiveButton = AddButton("Archive", PRSDesktop.Resources.archive.AsBitmapImage(), ArchiveButton_Clicked);
  155. ArchiveButton.IsEnabled = false;
  156. }
  157. private DynamicActionColumn AddDoubleColumn(Expression<Func<JobRequisitionItem, object>> property, string header)
  158. {
  159. var col = new DynamicTextColumn<JobRequisitionItem>(property) { Format = "F2", HeaderText = header, Width = 50 };
  160. ActionColumns.Add(col);
  161. return col;
  162. }
  163. protected override IDynamicGridUIComponent<JobRequisitionItem> CreateUIComponent()
  164. {
  165. return new UIComponent(this);
  166. }
  167. protected override void DoReconfigure(DynamicGridOptions options)
  168. {
  169. base.DoReconfigure(options);
  170. options.FilterRows = true;
  171. options.SelectColumns = true;
  172. options.RecordCount = true;
  173. options.DragSource = true;
  174. options.AddRows = false;
  175. options.ImportData = false;
  176. options.ExportData = false;
  177. options.Print = false;
  178. options.ShowHelp = false;
  179. }
  180. private void GridOnFilterSelected(DynamicGridSelectedFilterSettings settings)
  181. {
  182. new UserConfiguration<ReservationManagementUserSettings>().Save(new ReservationManagementUserSettings { Filters = settings });
  183. Refresh(false, true);
  184. }
  185. public override DynamicGridColumns GenerateColumns()
  186. {
  187. var columns = new DynamicGridColumns();
  188. columns.Add<JobRequisitionItem, DateTime>(x => x.Requisition.DueDate, 80, "Due", "", Alignment.MiddleCenter);
  189. columns.Add<JobRequisitionItem, string>(x => x.Requisition.Job.JobNumber, 70, "Job", "", Alignment.MiddleCenter);
  190. columns.Add<JobRequisitionItem, int>(x => x.Requisition.Number, 50, "Requi", "", Alignment.MiddleCenter);
  191. columns.Add<JobRequisitionItem, string>(x => x.Product.Code, 100, "Product Code", "", Alignment.MiddleLeft);
  192. columns.Add<JobRequisitionItem, string>(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleLeft);
  193. columns.Add<JobRequisitionItem, string>(x => x.Style.Code, 100, "Style", "", Alignment.MiddleLeft);
  194. columns.Add<JobRequisitionItem, string>(x => x.Dimensions.UnitSize, 70, "Size", "", Alignment.MiddleLeft);
  195. columns.Add<JobRequisitionItem, JobRequisitionItemStatus>(x => x.Status, 90, "Status", "", Alignment.MiddleCenter);
  196. return columns;
  197. }
  198. protected override void ConfigureColumnGroups()
  199. {
  200. GetColumnGrouping()
  201. .AddGroup("Stock", InStockColumn, OnOrderColumn)
  202. .AddGroup("Treatment", TreatmentRequiredColumn, TreatmentOnOrderColumn)
  203. .AddGroup("Allocated", AllocatedColumn, IssuedColumn);
  204. }
  205. protected override void SelectItems(CoreRow[]? rows)
  206. {
  207. base.SelectItems(rows);
  208. if(rows?.Any() == true)
  209. {
  210. ArchiveButton.IsEnabled = true;
  211. }
  212. }
  213. #region Action Column Buttons
  214. private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
  215. {
  216. column.AddItem("Order Required", PRSDesktop.Resources.purchase, OrderRequired_Clicked);
  217. column.AddItem("Split Line", PRSDesktop.Resources.split, SplitLine_Clicked);
  218. column.AddItem("Archive", PRSDesktop.Resources.archive, Archive_Clicked);
  219. if (Security.CanView<StockMovement>())
  220. {
  221. column.AddSeparator();
  222. column.AddItem("View Stock Movements", PRSDesktop.Resources.forklift, ViewStockMovements);
  223. }
  224. }
  225. private bool CheckValidAction(JobRequisitionItem item)
  226. {
  227. bool valid = true;
  228. if (item.Status == JobRequisitionItemStatus.Allocated)
  229. {
  230. MessageWindow.ShowMessage("Item has already been reserved!", "Error", image: MessageWindow.WarningImage);
  231. return false;
  232. }
  233. else if (item.Status == JobRequisitionItemStatus.OnOrder)
  234. {
  235. MessageWindow.ShowMessage("Item is already on order!", "Error", image: MessageWindow.WarningImage);
  236. return false;
  237. }
  238. else if (item.InStock >= item.Qty)
  239. {
  240. MessageWindow.ShowMessage("Item is already in stock!", "Error", image: MessageWindow.WarningImage);
  241. return false;
  242. }
  243. return valid;
  244. }
  245. private void SplitLine(JobRequisitionItem item, double oldItemQty, double newItemQty, string notes)
  246. {
  247. var items = new List<JobRequisitionItem>();
  248. var newItem = new JobRequisitionItem();
  249. newItem.Job.CopyFrom(item.Job);
  250. newItem.Requisition.ID = item.Requisition.ID;
  251. newItem.Requisition.Job.ID = item.Requisition.Job.ID;
  252. newItem.Product.ID = item.Product.ID;
  253. newItem.Dimensions.CopyFrom(item.Dimensions);
  254. newItem.Style.ID = item.Style.ID;
  255. newItem.Notes = item.Notes + Environment.NewLine + notes;
  256. item.Notes = newItem.Notes;
  257. item.Qty = oldItemQty;
  258. newItem.Qty = newItemQty;
  259. items.Add(newItem);
  260. items.Add(item);
  261. Client.Save(items, "Split lines from Job Requi Item Review Dashboard");
  262. MessageWindow.ShowMessage($"Line split - original line Qty is now {item.Qty}. New line Qty is {newItem.Qty}", "Lines split");
  263. Refresh(false, true);
  264. }
  265. private void SplitLine_Clicked(CoreRow? row)
  266. {
  267. if (row is null) return;
  268. var item = row.ToObject<JobRequisitionItem>();
  269. if (CheckValidAction(item))
  270. {
  271. var units = item.Qty - item.InStock;
  272. if(DoubleEdit.Execute("Enter amount to split", 1, units, ref units))
  273. {
  274. SplitLine(item, item.Qty - units, units, "Line split");
  275. }
  276. }
  277. }
  278. private static bool Archive(IEnumerable<JobRequisitionItem> items)
  279. {
  280. var itemsList = items.AsIList();
  281. var toChange = new List<JobRequisitionItem>();
  282. foreach(var item in itemsList)
  283. {
  284. if (item.Status != JobRequisitionItemStatus.Allocated)
  285. {
  286. var win = MessageWindow.New()
  287. .Message($"Requisition item for requisition {item.Requisition.Number} is not fully allocated; " +
  288. $"its current status is {item.Status}. Are you sure you wish to archive this item?")
  289. .Title("Confirm Archive")
  290. .AddYesButton("Archive");
  291. if(itemsList.Count > 1)
  292. {
  293. win.AddNoButton("Skip");
  294. }
  295. var result = win
  296. .AddCancelButton()
  297. .Display().Result;
  298. if (result == MessageWindowResult.Cancel)
  299. {
  300. return false;
  301. }
  302. else if(result == MessageWindowResult.Yes)
  303. {
  304. toChange.Add(item);
  305. }
  306. }
  307. else
  308. {
  309. toChange.Add(item);
  310. }
  311. }
  312. if(toChange.Count == 0)
  313. {
  314. MessageWindow.ShowMessage("No items archived.", "Cancelled");
  315. return false;
  316. }
  317. foreach(var item in toChange)
  318. {
  319. item.Archived = DateTime.Now;
  320. item.Notes += Environment.NewLine + "Line marked as Archived by " + App.EmployeeName + " on " + DateTime.Now.ToString("dd MMM yy");
  321. }
  322. Client.Save(toChange, "Updated From Job Requisition Review Dashboard");
  323. return true;
  324. }
  325. private bool ArchiveButton_Clicked(Button _btn, CoreRow[] rows)
  326. {
  327. return Archive(rows.ToObjects<JobRequisitionItem>());
  328. }
  329. private void Archive_Clicked(CoreRow? row)
  330. {
  331. if (row is null) return;
  332. if (Archive(CoreUtils.One(row.ToObject<JobRequisitionItem>())))
  333. {
  334. Refresh(false, true);
  335. }
  336. }
  337. private void OrderRequired_Clicked(CoreRow? row)
  338. {
  339. if (row is null) return;
  340. var item = row.ToObject<JobRequisitionItem>();
  341. if (CheckValidAction(item))
  342. {
  343. item.OrderRequired = DateTime.Now;
  344. item.Notes += Environment.NewLine + "Line marked as Order Required by " + App.EmployeeName + " on " + DateTime.Now.ToString("dd MMM yy");
  345. Client.Save(item, "Updated From Job Requisition Review Dashboard");
  346. Refresh(false, true);
  347. }
  348. }
  349. private void ViewStockMovements(CoreRow? row)
  350. {
  351. if (row is null) return;
  352. var requiID = row.Get<StockMovement, Guid>(x => x.ID);
  353. DynamicDataGrid<StockMovement> grid;
  354. if (DynamicGridUtils.TryFindDynamicGrid(typeof(DynamicDataGrid<>), typeof(StockMovement), out var gridType))
  355. {
  356. grid = (Activator.CreateInstance(gridType) as DynamicDataGrid<StockMovement>)!;
  357. }
  358. else
  359. {
  360. grid = new DynamicDataGrid<StockMovement>();
  361. grid.OnReconfigure += (options) =>
  362. {
  363. options.SelectColumns = true;
  364. };
  365. grid.Reconfigure();
  366. }
  367. grid.OnDefineFilter += (t) => new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(requiID);
  368. var window = DynamicGridUtils.CreateGridWindow("Stock movements", grid);
  369. window.ShowDialog();
  370. }
  371. #endregion
  372. protected override void Reload(Filters<JobRequisitionItem> criteria, Columns<JobRequisitionItem> columns, ref SortOrder<JobRequisitionItem>? sort, Action<CoreTable?, Exception?> action)
  373. {
  374. criteria.Add(new Filter<JobRequisitionItem>(x => x.Requisition.Approved).IsNotEqualTo(DateTime.MinValue));
  375. criteria.Add(new Filter<JobRequisitionItem>(x => x.Archived).IsEqualTo(DateTime.MinValue));
  376. criteria.Add(new Filter<JobRequisitionItem>(x => x.Cancelled).IsEqualTo(DateTime.MinValue));
  377. sort = new SortOrder<JobRequisitionItem>(x => x.Requisition.Number, SortDirection.Descending);
  378. base.Reload(criteria, columns, ref sort, action);
  379. }
  380. protected override DragDropEffects OnRowsDragStart(CoreRow[] rows)
  381. {
  382. // Only allow dragging the selected rows.
  383. var selected = SelectedRows.Select(x => x.Get<JobRequisitionItem, Guid>(x => x.ID)).ToHashSet();
  384. var draggedRows = rows.Where(x => selected.Contains(x.Get<JobRequisitionItem, Guid>(x => x.ID))).ToArray();
  385. if(draggedRows.Length == 0)
  386. {
  387. return DragDropEffects.None;
  388. }
  389. else
  390. {
  391. return base.OnRowsDragStart(draggedRows);
  392. }
  393. }
  394. }
  395. public class JobRequiReviewDashboardFilterItem
  396. {
  397. public Guid SupplierID { get; set; }
  398. public Guid ProductID { get; set; }
  399. public string Text { get; set; }
  400. public JobRequiReviewDashboardFilterItem()
  401. {
  402. SupplierID = Guid.Empty;
  403. ProductID = Guid.Empty;
  404. Text = "";
  405. }
  406. }