JobSummaryGrid.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. using com.sun.corba.se.spi.orbutil.threadpool;
  2. using Comal.Classes;
  3. using InABox.Clients;
  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.Diagnostics.CodeAnalysis;
  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 PRSDesktop.Panels.Jobs.Summary;
  17. using System.Threading;
  18. namespace PRSDesktop;
  19. internal class JobSummaryGrid : DynamicDataGrid<JobMaterial>, IMasterDetailControl<Job,JobMaterial>, IDataModelSource
  20. {
  21. Guid empID = Guid.Empty;
  22. string empName = "";
  23. private List<DetailsColumn> DetailsColumns;
  24. private Job? _master;
  25. private Button _cancelRequisitions;
  26. private Button _releaseStock;
  27. public Job? Master
  28. {
  29. get => _master;
  30. set
  31. {
  32. _master = value;
  33. _cancelRequisitions.IsEnabled = Master?.JobStatus.Active == false;
  34. }
  35. }
  36. public Filter<JobMaterial> MasterDetailFilter => (Master?.ID ?? Guid.Empty) != Guid.Empty
  37. ? new Filter<JobMaterial>(x => x.Job.ID).IsEqualTo(Master.ID)
  38. .And(x => x.Product.ID).IsNotEqualTo(Guid.Empty)
  39. : new Filter<JobMaterial>().None();
  40. public bool IncludeReserves { get; set; }
  41. public bool ShowIssues { get; set; }
  42. public JobSummaryGrid() : base()
  43. {
  44. ColumnsTag = nameof(JobSummaryGrid);
  45. OnCellDoubleClick += JobSummaryGrid_OnCellDoubleClick;
  46. SetupDetailsColumns();
  47. _cancelRequisitions = AddButton("Cancel Requisitions", PRSDesktop.Resources.archive.AsBitmapImage(),
  48. CancelRequisitions);
  49. _releaseStock = AddButton("Release Stock", PRSDesktop.Resources.archive.AsBitmapImage(),
  50. ReleaseStock);
  51. }
  52. public Dictionary<StockHolding,IEnumerable<JobRequisitionItem>> GetHoldings(CoreRow[] rows)
  53. {
  54. var result = new Dictionary<StockHolding,IEnumerable<JobRequisitionItem>>();
  55. foreach (var row in rows)
  56. {
  57. var material = row.ToObject<JobMaterial>();
  58. var filter = new Filter<StockHolding>(x => x.Product.ID).IsEqualTo(material.Product.ID);
  59. if (StyleColumnVisible())
  60. filter = filter.And(x => x.Style.ID).IsEqualTo(material.Style.ID);
  61. if(DimensionsColumnVisible())
  62. filter = filter.And(x => x.Dimensions).DimensionEquals(material.Dimensions);
  63. var columns = Columns.None<StockHolding>().Add(x => x.Location.ID)
  64. .Add(x => x.Product.ID)
  65. .Add(x => x.Style.ID)
  66. .AddDimensionsColumns(x=>x.Dimensions);
  67. foreach (var holding in new Client<StockHolding>().Query(filter, columns).ToObjects<StockHolding>())
  68. result[holding] = holding.LoadRequisitionItems(true);
  69. }
  70. return result;
  71. }
  72. private bool ReleaseStock(System.Windows.Controls.Button btn, CoreRow[] rows)
  73. {
  74. if (rows?.Any() != true)
  75. return false;
  76. if(MessageWindow.ShowYesNoCancel("This will release any stock holdings for the selected items!\n\nAre you sure you wish to do this? ", "Confirm") != MessageWindowResult.Yes)
  77. return false;
  78. var updates = new List<StockMovement>();
  79. Progress.ShowModal("Loading Holdings", progress =>
  80. {
  81. var holdings = GetHoldings(rows);
  82. foreach (var (holding, items) in holdings)
  83. {
  84. foreach (var item in items)
  85. {
  86. var from = holding.CreateMovement();
  87. //from.OrderItem.ID = group.Key.OrderItem;
  88. from.JobRequisitionItem.ID = item.ID;
  89. from.Cost = StockRelease == StockReleaseWriteDownMethod.AverageCost
  90. ? holding.AverageValue
  91. : 0.0;
  92. from.Type = StockMovementType.TransferOut;
  93. from.Job.ID = Master.ID;
  94. from.Issued = item.Qty;
  95. var to = holding.CreateMovement();
  96. to.Cost = StockRelease == StockReleaseWriteDownMethod.AverageCost
  97. ? holding.AverageValue
  98. : 0.0;
  99. //to.OrderItem.ID = group.Key.OrderItem;
  100. //to.JobRequisitionItem.ID = item.ID;
  101. to.Job.ID = Guid.Empty;
  102. to.Type = StockMovementType.TransferIn;
  103. to.Transaction = from.Transaction;
  104. to.Received = item.Qty;
  105. to.Notes = $"Released from job {Master.JobNumber}";
  106. updates.Add(from);
  107. updates.Add(to);
  108. }
  109. }
  110. progress.Report("Saving Movements");
  111. if (updates.Any())
  112. Client.Save(updates, $"Stock Released from {Master.JobNumber} on Job Summary Screen");
  113. });
  114. MessageWindow.ShowMessage("The selected stock holdings have been cancelled.","Done");
  115. return updates.Any();
  116. }
  117. private bool CancelRequisitions(System.Windows.Controls.Button btn, CoreRow[] rows)
  118. {
  119. if (rows?.Any() != true)
  120. return false;
  121. if(MessageWindow.ShowYesNoCancel("This will cancel all outstanding requisitions for the selected items!\n\nAre you sure you wish to do this? ", "Confirm") != MessageWindowResult.Yes)
  122. return false;
  123. var updates = new List<JobRequisitionItem>();
  124. Progress.ShowModal("Loading Holdings", progress =>
  125. {
  126. var holdings = GetHoldings(rows);
  127. foreach (var holding in holdings)
  128. {
  129. var items = holding.Value.Where(
  130. x => x.ID != Guid.Empty
  131. && x.Status != JobRequisitionItemStatus.Cancelled
  132. && x.Status != JobRequisitionItemStatus.Issued
  133. && x.Status != JobRequisitionItemStatus.Archived
  134. );
  135. foreach (var item in items)
  136. {
  137. item.Cancelled = DateTime.Now;
  138. updates.Add(item);
  139. }
  140. progress.Report("Cancelling Requisitions");
  141. Client.Save(updates, $"Requsition cancelled for {Master.JobNumber} from Job Summary Screen");
  142. }
  143. });
  144. MessageWindow.ShowMessage("The selected requisitions have been cancelled.","Done");
  145. return updates.Any();
  146. }
  147. private class UIComponent : DynamicGridGridUIComponent<JobMaterial>
  148. {
  149. private Column<JobMaterial> _jobshortage = new Column<JobMaterial>(x => x.JobShortage);
  150. private Column<JobMaterial> _stockshortage = new Column<JobMaterial>(x => x.FreeStockShortage);
  151. protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
  152. {
  153. if (column is DynamicGridColumn col)
  154. {
  155. if (_jobshortage.IsEqualTo(col.ColumnName))
  156. return row.Get<JobMaterial, double>(x => x.JobShortage) > 0.0F
  157. ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
  158. : null;
  159. if (_stockshortage.IsEqualTo(col.ColumnName))
  160. return row.Get<JobMaterial, double>(x => x.FreeStockShortage) > 0.0F
  161. ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
  162. : null;
  163. }
  164. return null;
  165. }
  166. }
  167. protected override IDynamicGridUIComponent<JobMaterial> CreateUIComponent()
  168. {
  169. return new UIComponent()
  170. {
  171. Parent = this
  172. };
  173. }
  174. protected override void Init()
  175. {
  176. base.Init();
  177. HiddenColumns.Add(x => x.Product.ID);
  178. HiddenColumns.Add(x => x.Style.ID);
  179. HiddenColumns.Add(x => x.Dimensions.UnitSize);
  180. HiddenColumns.Add(x => x.Dimensions.Value);
  181. HiddenColumns.Add(x => x.BillOfMaterials);
  182. HiddenColumns.Add(x => x.Requisitions);
  183. HiddenColumns.Add(x => x.PickingLists);
  184. HiddenColumns.Add(x => x.Issued);
  185. HiddenColumns.Add(x => x.ReservedStock);
  186. HiddenColumns.Add(x => x.OnOrder);
  187. HiddenColumns.Add(x => x.JobShortage);
  188. HiddenColumns.Add(x => x.FreeOnHand);
  189. HiddenColumns.Add(x => x.FreeOnOrder);
  190. HiddenColumns.Add(x => x.FreeStockTotal);
  191. HiddenColumns.Add(x => x.FreeStockShortage);
  192. HiddenColumns.Add(x => x.Product.Image.ID);
  193. HiddenColumns.Add(x => x.Product.Image.FileName);
  194. ActionColumns.Add(new DynamicImageManagerColumn<JobMaterial>(this, x => x.Product.Image, false)
  195. { Position = DynamicActionColumnPosition.Start });
  196. ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
  197. AddButton("Create PO", null, CreatePO);
  198. }
  199. protected override void DoReconfigure(DynamicGridOptions options)
  200. {
  201. base.DoReconfigure(options);
  202. options.RecordCount = true;
  203. options.SelectColumns = true;
  204. options.FilterRows = true;
  205. options.ExportData = true;
  206. options.MultiSelect = true;
  207. }
  208. private bool StyleColumnVisible()
  209. {
  210. var styleColumn = CoreUtils.GetFullPropertyName<JobMaterial, ProductStyleLink>(x => x.Style, ".");
  211. return VisibleColumns.Any(x => x.ColumnName.StartsWith(styleColumn));
  212. }
  213. private bool DimensionsColumnVisible()
  214. {
  215. var dimColumn = CoreUtils.GetFullPropertyName<JobMaterial, StockDimensions>(x => x.Dimensions, ".");
  216. return VisibleColumns.Any(x => x.ColumnName.StartsWith(dimColumn));
  217. }
  218. private bool? _hasUnitSize;
  219. private bool UnitSizeColumnVisible()
  220. {
  221. if (_hasUnitSize.HasValue)
  222. {
  223. return _hasUnitSize.Value;
  224. }
  225. var dimColumn = CoreUtils.GetFullPropertyName<JobMaterial, string>(x => x.Dimensions.UnitSize, ".");
  226. _hasUnitSize = VisibleColumns.Any(x => x.ColumnName.Equals(dimColumn));
  227. return _hasUnitSize.Value;
  228. }
  229. protected override DynamicGridColumns LoadColumns()
  230. {
  231. _hasUnitSize = null;
  232. return base.LoadColumns();
  233. }
  234. public override DynamicGridColumns GenerateColumns()
  235. {
  236. var columns = new DynamicGridColumns();
  237. columns.Add<JobMaterial, string>(x => x.Product.Group.Code, 120, "Group Code", "", Alignment.MiddleCenter);
  238. columns.Add<JobMaterial, string>(x => x.Product.Code, 200, "Product Code", "", Alignment.MiddleCenter);
  239. columns.Add<JobMaterial, string>(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleCenter);
  240. columns.Add<JobMaterial, string>(x => x.Dimensions.UnitSize, 120, "UOM", "", Alignment.MiddleCenter);
  241. columns.Add<JobMaterial, double>(x => x.BillOfMaterials, 80, "BOM", "", Alignment.MiddleCenter);
  242. columns.Add<JobMaterial, double>(x => x.Requisitions, 80, "Req.", "", Alignment.MiddleCenter);
  243. columns.Add<JobMaterial, double>(x => x.PickingLists, 80, "P/L", "", Alignment.MiddleCenter);
  244. columns.Add<JobMaterial, double>(x => x.Issued, 80, "Issued", "", Alignment.MiddleCenter);
  245. columns.Add<JobMaterial, double>(x => x.ReservedStock, 80, "Reserved", "", Alignment.MiddleCenter);
  246. columns.Add<JobMaterial, double>(x => x.OnOrder, 80, "Ordered", "", Alignment.MiddleCenter);
  247. columns.Add<JobMaterial, double>(x => x.JobShortage, 80, "Job Short", "", Alignment.MiddleCenter);
  248. columns.Add<JobMaterial, double>(x => x.FreeStockTotal, 80, "Free Stock", "", Alignment.MiddleCenter);
  249. columns.Add<JobMaterial, double>(x => x.FreeStockShortage, 80, "Stk Short", "", Alignment.MiddleCenter);
  250. return columns;
  251. }
  252. private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
  253. {
  254. if (row is null) return;
  255. var menu = column.GetMenu();
  256. menu.AddItem("View Stock Movements", PRSDesktop.Resources.forklift, row, ViewStockMovements_Click);
  257. foreach(var col in DetailsColumns)
  258. {
  259. menu.AddItem(col.MenuText, null, row, col.Action);
  260. }
  261. }
  262. private void ViewStockMovements_Click(CoreRow row)
  263. {
  264. var item = row.ToObject<JobMaterial>();
  265. var productID = item.Product.ID;
  266. Guid? styleID = StyleColumnVisible() ? item.Style.ID : null;
  267. var dimensions = DimensionsColumnVisible() ? item.Dimensions : null;
  268. /*ShowDetailGrid<StockMovement>(
  269. args.Column.ColumnName,
  270. x => x.Product.ID,
  271. productid,
  272. x => x.Style.ID,
  273. styleid,
  274. x => x.Dimensions.UnitSize,
  275. unitsize,
  276. x => x.Job.ID,
  277. new Filter<StockMovement>(x => x.IsTransfer).IsEqualTo(false).And(x => x.Issued).IsNotEqualTo(0.0F),
  278. null
  279. );
  280. var movements = Client.Query<StockMovement>(
  281. );*/
  282. var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(StockMovement))) as DynamicDataGrid<StockMovement>);
  283. if (grid == null)
  284. {
  285. MessageBox.Show($"Cannot create Grid for [{typeof(StockMovement).Name}]");
  286. return;
  287. }
  288. grid.ColumnsTag = $"{ColumnsTag}.Transactions";
  289. grid.Reconfigure(options =>
  290. {
  291. options.Clear();
  292. options.FilterRows = true;
  293. options.SelectColumns = true;
  294. });
  295. grid.OnReload += (object sender, Filters<StockMovement> criteria, Columns<StockMovement> columns, ref SortOrder<StockMovement>? sortby) =>
  296. {
  297. var filter = criteria.Combine();
  298. // We want to show entire transactions.
  299. criteria.Clear();
  300. criteria.Add(new Filter<StockMovement>(x => x.Transaction).InQuery(filter, x => x.Transaction));
  301. };
  302. grid.OnDefineFilter += t =>
  303. {
  304. var filter = new Filter<StockMovement>(x => x.Product.ID).IsEqualTo(productID);
  305. if(dimensions is not null)
  306. filter = filter.And(x => x.Dimensions).DimensionEquals(dimensions);
  307. if (styleID.HasValue)
  308. filter = filter.And(x => x.Style.ID).IsEqualTo(styleID);
  309. filter = filter.And(x => x.Job.ID).IsEqualTo(Master?.ID ?? Guid.Empty);
  310. return filter;
  311. };
  312. var window = DynamicGridUtils.CreateGridWindow($"Stock Movements", grid);
  313. window.ShowDialog();
  314. }
  315. private void ShowDetailGrid<TEntity>(
  316. String columnname,
  317. Expression<Func<TEntity, object?>> productcol,
  318. Guid productid,
  319. Expression<Func<TEntity, object?>> stylecol,
  320. Guid? styleid,
  321. Expression<Func<TEntity, IDimensions>> dimcol,
  322. IDimensions? dimensions,
  323. Expression<Func<TEntity, object?>>? jobcol,
  324. Filter<TEntity>? extrafilter,
  325. Func<CoreRow, bool>? rowfilter
  326. )
  327. {
  328. var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
  329. if (grid == null)
  330. {
  331. MessageBox.Show($"Cannot create Grid for [{typeof(TEntity).Name}]");
  332. return;
  333. }
  334. grid.ColumnsTag = $"{ColumnsTag}.{columnname}";
  335. grid.Reconfigure(options =>
  336. {
  337. options.Clear();
  338. options.FilterRows = true;
  339. options.SelectColumns = true;
  340. });
  341. grid.OnDefineFilter += t =>
  342. {
  343. var filter = new Filter<TEntity>(productcol).IsEqualTo(productid);
  344. if(dimensions is not null)
  345. filter = filter.And(CoreUtils.GetFullPropertyName(dimcol, ".")).DimensionEquals(dimensions);
  346. if (styleid.HasValue)
  347. filter = filter.And(stylecol).IsEqualTo(styleid);
  348. if (jobcol != null)
  349. filter = filter.And(jobcol).IsEqualTo(Master?.ID ?? Guid.Empty);
  350. if (extrafilter != null)
  351. filter = filter.And(extrafilter);
  352. return filter;
  353. };
  354. grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
  355. var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(columnname)} Calculation", grid);
  356. window.ShowDialog();
  357. }
  358. private static readonly Column<JobMaterial> BOMColumn = new Column<JobMaterial>(x => x.BillOfMaterials);
  359. private static readonly Column<JobMaterial> RequisitionsColumn = new Column<JobMaterial>(x => x.Requisitions);
  360. private static readonly Column<JobMaterial> PickingListsColumn = new Column<JobMaterial>(x => x.PickingLists);
  361. private static readonly Column<JobMaterial> IssuedColumn = new Column<JobMaterial>(x => x.Issued);
  362. private static readonly Column<JobMaterial> ReservedStockColumn = new Column<JobMaterial>(x => x.ReservedStock);
  363. private static readonly Column<JobMaterial> OnOrderColumn = new Column<JobMaterial>(x => x.OnOrder);
  364. private static readonly Column<JobMaterial> FreeOnHandColumn = new Column<JobMaterial>(x => x.FreeOnHand);
  365. private static readonly Column<JobMaterial> FreeOnOrderColumn = new Column<JobMaterial>(x => x.FreeOnOrder);
  366. private void ViewBillOfMaterials(CoreRow row)
  367. {
  368. var item = row.ToObject<JobMaterial>();
  369. ShowDetailGrid<JobBillOfMaterialsItem>(
  370. BOMColumn.Property,
  371. x => x.Product.ID,
  372. item.Product.ID,
  373. x => x.Style.ID,
  374. StyleColumnVisible() ? item.Style.ID : null,
  375. x => x.Dimensions,
  376. DimensionsColumnVisible() ? item.Dimensions : null,
  377. x => x.Job.ID,
  378. new Filter<JobBillOfMaterialsItem>(x => x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
  379. null
  380. );
  381. }
  382. private void ViewRequisitions(CoreRow row)
  383. {
  384. var item = row.ToObject<JobMaterial>();
  385. Guid? styleID = StyleColumnVisible() ? item.Style.ID : null;
  386. var dimensions = DimensionsColumnVisible() ? item.Dimensions : null;
  387. var grid = new JobRequisitionItemSummaryGrid();
  388. grid.OnDefineFilter += t =>
  389. {
  390. var filter = new Filter<JobRequisitionItem>(x => x.Product.ID).IsEqualTo(item.Product.ID)
  391. .And(x => x.Requisition.Approved).IsNotEqualTo(DateTime.MinValue)
  392. .And(x => x.Cancelled).IsEqualTo(DateTime.MinValue);
  393. if(dimensions is not null)
  394. {
  395. filter = filter.And(x => x.Dimensions).DimensionEquals(item.Dimensions);
  396. }
  397. if (styleID.HasValue)
  398. {
  399. filter = filter.And(x => x.Style.ID).IsEqualTo(styleID.Value);
  400. }
  401. filter = filter.And(x => x.Requisition.Job.ID).IsEqualTo(Master?.ID ?? Guid.Empty);
  402. return filter;
  403. };
  404. var window = DynamicGridUtils.CreateGridWindow($"Viewing Requisition Calculation", grid);
  405. window.ShowDialog();
  406. }
  407. private void ViewPickingLists(CoreRow row)
  408. {
  409. var item = row.ToObject<JobMaterial>();
  410. ShowDetailGrid<RequisitionItem>(
  411. PickingListsColumn.Property,
  412. x => x.Product.ID,
  413. item.Product.ID,
  414. x => x.Style.ID,
  415. StyleColumnVisible() ? item.Style.ID : null,
  416. x => x.Dimensions,
  417. DimensionsColumnVisible() ? item.Dimensions : null,
  418. x => x.JobLink.ID,
  419. new Filter<RequisitionItem>(x => x.RequisitionLink.Filled).IsEqualTo(DateTime.MinValue),
  420. null
  421. );
  422. }
  423. private void ViewIssued(CoreRow row)
  424. {
  425. var item = row.ToObject<JobMaterial>();
  426. ShowDetailGrid<StockMovement>(
  427. IssuedColumn.Property,
  428. x => x.Product.ID,
  429. item.Product.ID,
  430. x => x.Style.ID,
  431. StyleColumnVisible() ? item.Style.ID : null,
  432. x => x.Dimensions,
  433. DimensionsColumnVisible() ? item.Dimensions : null,
  434. x => x.Job.ID,
  435. new Filter<StockMovement>(x => x.Type).IsEqualTo(StockMovementType.Issue),
  436. null
  437. );
  438. }
  439. private void ViewReservedStock(CoreRow row)
  440. {
  441. var item = row.ToObject<JobMaterial>();
  442. ShowDetailGrid<StockHolding>(
  443. ReservedStockColumn.Property,
  444. x => x.Product.ID,
  445. item.Product.ID,
  446. x => x.Style.ID,
  447. StyleColumnVisible() ? item.Style.ID : null,
  448. x => x.Dimensions,
  449. DimensionsColumnVisible() ? item.Dimensions : null,
  450. x => x.Job.ID,
  451. new Filter<StockHolding>(x => x.Units).IsGreaterThan(0.1),
  452. null
  453. );
  454. }
  455. private void ViewOnOrder(CoreRow row)
  456. {
  457. var item = row.ToObject<JobMaterial>();
  458. ShowDetailGrid<PurchaseOrderItemAllocation>(
  459. OnOrderColumn.Property,
  460. x => x.Item.Product.ID,
  461. item.Product.ID,
  462. x => x.Item.Style.ID,
  463. StyleColumnVisible() ? item.Style.ID : null,
  464. x => x.Item.Dimensions,
  465. DimensionsColumnVisible() ? item.Dimensions : null,
  466. x => x.Job.ID,
  467. null,
  468. null
  469. );
  470. }
  471. private void ViewFreeOnHand(CoreRow row)
  472. {
  473. var item = row.ToObject<JobMaterial>();
  474. ShowDetailGrid<StockHolding>(
  475. FreeOnHandColumn.Property,
  476. x => x.Product.ID,
  477. item.Product.ID,
  478. x => x.Style.ID,
  479. StyleColumnVisible() ? item.Style.ID : null,
  480. x => x.Dimensions,
  481. DimensionsColumnVisible() ? item.Dimensions : null,
  482. null,
  483. new Filter<StockHolding>(x => x.Units).IsNotEqualTo(0.0F)
  484. .And(
  485. IncludeReserves
  486. ? new Filter<StockHolding>(x => x.Job.ID).IsNotEqualTo(Master?.ID ?? Guid.Empty)
  487. : new Filter<StockHolding>(x => x.Job.JobStatus.Active).IsEqualTo(false)
  488. ),
  489. null
  490. );
  491. }
  492. private void ViewFreeOnOrder(CoreRow row)
  493. {
  494. var item = row.ToObject<JobMaterial>();
  495. ShowDetailGrid<PurchaseOrderItemAllocation>(
  496. FreeOnOrderColumn.Property,
  497. x => x.Item.Product.ID,
  498. item.Product.ID,
  499. x => x.Item.Style.ID,
  500. StyleColumnVisible() ? item.Style.ID : null,
  501. x => x.Item.Dimensions,
  502. DimensionsColumnVisible() ? item.Dimensions : null,
  503. null,
  504. IncludeReserves
  505. ? new Filter<PurchaseOrderItemAllocation>(x => x.Job.ID).IsNotEqualTo(Master?.ID ?? Guid.Empty)
  506. : new Filter<PurchaseOrderItemAllocation>(x => x.Job.JobStatus.Active).IsEqualTo(false),
  507. null
  508. );
  509. }
  510. [MemberNotNull(nameof(DetailsColumns))]
  511. private void SetupDetailsColumns()
  512. {
  513. DetailsColumns = new List<DetailsColumn>
  514. {
  515. new(BOMColumn, ViewBillOfMaterials, "View bill of materials"),
  516. new(RequisitionsColumn, ViewRequisitions, "View requisitions"),
  517. new(PickingListsColumn, ViewPickingLists, "View picking lists"),
  518. new(IssuedColumn, ViewIssued, "View issued"),
  519. new(ReservedStockColumn, ViewReservedStock, "View reserved stock"),
  520. new(OnOrderColumn, ViewOnOrder, "View on order"),
  521. new(FreeOnHandColumn, ViewFreeOnHand, "View free on hand"),
  522. new(FreeOnOrderColumn, ViewFreeOnOrder, "View free on order"),
  523. };
  524. }
  525. private class DetailsColumn
  526. {
  527. public Column<JobMaterial> Column { get; set; }
  528. public Action<CoreRow> Action { get; set; }
  529. public string MenuText { get; set; }
  530. public DetailsColumn(Column<JobMaterial> column, Action<CoreRow> action, string menuText)
  531. {
  532. Column = column;
  533. Action = action;
  534. MenuText = menuText;
  535. }
  536. }
  537. private void JobSummaryGrid_OnCellDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  538. {
  539. if (args?.Column is DynamicGridColumn col)
  540. {
  541. foreach (var column in DetailsColumns)
  542. {
  543. if (column.Column.IsEqualTo(col.ColumnName))
  544. {
  545. column.Action(args.Row);
  546. break;
  547. }
  548. }
  549. }
  550. }
  551. public event DataModelUpdateEvent? OnUpdateDataModel;
  552. public string SectionName => "Job Summary";
  553. public StockReleaseWriteDownMethod StockRelease { get; set; }
  554. public DataModel DataModel(Selection selection)
  555. {
  556. return new AutoDataModel<JobMaterial>(MasterDetailFilter);
  557. }
  558. public override JobMaterial CreateItem()
  559. {
  560. var result = base.CreateItem();
  561. result.Job.ID = Master?.ID ?? Guid.Empty;
  562. result.Job.Synchronise(Master ?? new Job());
  563. return result;
  564. }
  565. private class Key(Guid jobID, Guid productID, Guid? styleID, IDimensions? dimensions)
  566. {
  567. public Guid JobID { get; set; } = jobID;
  568. public Guid ProductID { get; set; } = productID;
  569. public Guid? StyleID { get; set; } = styleID;
  570. public IDimensions? Dimensions { get; set; } = dimensions;
  571. public override bool Equals(object? obj)
  572. {
  573. return obj is Key key
  574. && JobID == key.JobID
  575. && ProductID == key.ProductID
  576. && StyleID == key.StyleID
  577. && Equals(Dimensions, key.Dimensions);
  578. }
  579. public override int GetHashCode()
  580. {
  581. return HashCode.Combine(
  582. JobID, ProductID, StyleID, Dimensions);
  583. }
  584. }
  585. private Key[] GetKeys(IEnumerable<CoreRow> rows, Columns<JobMaterial> columns, bool hasstyle, bool hasDimensions)
  586. {
  587. int jobcol = columns.IndexOf(x => x.Job.ID);
  588. int productcol = columns.IndexOf(x => x.Product.ID);
  589. int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  590. var dimCols = hasDimensions ? Dimensions.GetFilterColumnIndices<JobMaterial>(columns, x => x.Dimensions) : null;
  591. var result = rows.Select(r => new Key(
  592. (Guid)(r.Values[jobcol] ?? Guid.Empty),
  593. (Guid)(r.Values[productcol] ?? Guid.Empty),
  594. (stylecol != -1) ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null,
  595. dimCols is not null ? r.ToDimensions<StockDimensions>(dimCols) : null)
  596. ).Distinct().ToArray();
  597. return result;
  598. }
  599. private CoreRow[] GetAllocationRows(IEnumerable<CoreRow> rows, Columns<PurchaseOrderItemAllocation> columns, Guid? jobID, Key key, Func<CoreRow, bool>? extrafilter = null)
  600. {
  601. int jobcol = columns.IndexOf(x => x.Job.ID);
  602. int productcol = columns.IndexOf(x => x.Item.Product.ID);
  603. int stylecol = key.StyleID.HasValue ? columns.IndexOf(x => x.Item.Style.ID) : -1;
  604. int unitcol = columns.IndexOf(x => x.Item.Dimensions.UnitSize);
  605. var dimCols = Dimensions.GetFilterColumnIndices<PurchaseOrderItemAllocation>(columns, x => x.Item.Dimensions);
  606. var subset = rows
  607. .Where(r =>
  608. (!jobID.HasValue || Guid.Equals(jobID, r.Values[jobcol]))
  609. && Guid.Equals(key.ProductID, r.Values[productcol])
  610. && (!key.StyleID.HasValue || Guid.Equals(key.StyleID, r.Values[stylecol]))
  611. && (key.Dimensions is null || key.Dimensions.Equals(r.ToDimensions<StockDimensions>(dimCols)))
  612. && ((extrafilter == null) || extrafilter(r))
  613. );
  614. return subset.ToArray();
  615. }
  616. private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid? jobID, Key key, Func<CoreRow, bool>? extrafilter = null) where TSource : IJobMaterial
  617. {
  618. int jobcol = columns.IndexOf(x => x.Job.ID);
  619. int productcol = columns.IndexOf(x => x.Product.ID);
  620. int stylecol = key.StyleID.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
  621. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  622. var dimCols = Dimensions.GetFilterColumnIndices<TSource>(columns, x => x.Dimensions);
  623. var subset = rows
  624. .Where(r =>
  625. (!jobID.HasValue || Guid.Equals(jobID, r.Values[jobcol]))
  626. && Guid.Equals(key.ProductID, r.Values[productcol])
  627. && (!key.StyleID.HasValue || Guid.Equals(key.StyleID, r.Values[stylecol]))
  628. && (key.Dimensions is null || key.Dimensions.Equals(r.ToDimensions<StockDimensions>(dimCols)))
  629. && ((extrafilter == null) || extrafilter(r))
  630. );
  631. return subset.ToArray();
  632. }
  633. private double Aggregate<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, bool hasstyle, bool hasjob, Expression<Func<TSource, object>> source, CoreRow target, Expression<Func<JobMaterial, object>> aggregate)
  634. {
  635. int srcol = columns.IndexOf(source);
  636. if (srcol == -1)
  637. return 0.00;
  638. var total = rows.Aggregate(0d, (value, row) => value + (double)(row.Values[srcol] ?? 0.0d));
  639. target.Set(aggregate, total);
  640. return total;
  641. }
  642. protected override void Reload(
  643. Filters<JobMaterial> criteria, Columns<JobMaterial> columns, ref SortOrder<JobMaterial>? sort,
  644. CancellationToken token, Action<CoreTable?, Exception?> action)
  645. {
  646. criteria.Add(MasterDetailFilter);
  647. criteria.Add(FilterComponent.GetFilter());
  648. var orderby = sort;
  649. Progress.ShowModal("Loading Data",
  650. (progress) =>
  651. {
  652. var table = new CoreTable();
  653. table.LoadColumns(columns);
  654. var data = new Client<JobMaterial>().Query(criteria.Combine(), columns, orderby);
  655. var pids = data.ExtractValues<JobMaterial, Guid>(x => x.Product.ID).ToArray();
  656. if (pids.Any())
  657. {
  658. var results = Client.QueryMultiple(
  659. new KeyedQueryDef<StockHolding>(
  660. new Filter<StockHolding>(x => x.Product.ID).InList(pids)
  661. .And(x => x.Units).IsNotEqualTo(0.0F)
  662. .And(new Filter<StockHolding>(x => x.Job.ID).IsEqualTo(Guid.Empty).Or(x => x.Job.ID).IsNotEqualTo(Master?.ID ?? Guid.Empty)),
  663. Columns.None<StockHolding>().Add(x => x.Product.ID)
  664. .Add(x => x.Style.ID)
  665. .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
  666. .Add(x => x.Units)
  667. .Add(x => x.Job.ID)
  668. .Add(x => x.Job.JobStatus.Active)),
  669. new KeyedQueryDef<PurchaseOrderItemAllocation>(
  670. new Filter<PurchaseOrderItemAllocation>(x => x.Item.ReceivedDate).IsEqualTo(DateTime.MinValue)
  671. .And(x => x.Item.Product.ID).InList(pids)
  672. .And(new Filter<PurchaseOrderItemAllocation>(x => x.Job.ID).IsEqualTo(Guid.Empty).Or(x => x.Job.ID).IsNotEqualTo(Master?.ID ?? Guid.Empty)),
  673. Columns.None<PurchaseOrderItemAllocation>().Add(x => x.Item.Product.ID)
  674. .Add(x => x.Item.Style.ID)
  675. .AddDimensionsColumns(x => x.Item.Dimensions, Dimensions.ColumnsType.Local)
  676. .Add(x => x.Quantity)
  677. .Add(x => x.Job.ID)
  678. .Add(x => x.Job.JobStatus.Active)));
  679. var freestock = results.Get<StockHolding>();
  680. var freestockcolumns = Columns.None<StockHolding>().Add(freestock.Columns.Select(x => x.ColumnName));
  681. var freeorders = results.Get<PurchaseOrderItemAllocation>();
  682. var freeordercolumns = Columns.None<PurchaseOrderItemAllocation>().Add(freeorders.Columns.Select(x => x.ColumnName));
  683. var hasStyle = StyleColumnVisible();
  684. var hasDimensions = DimensionsColumnVisible();
  685. var hasUnitSize = UnitSizeColumnVisible();
  686. var keys = GetKeys(data.Rows, columns, hasStyle, hasDimensions);
  687. foreach (var key in keys)
  688. {
  689. var rows = GetRows(data.Rows, columns, key.JobID, key);
  690. if (rows.Length != 0)
  691. {
  692. if (!hasUnitSize)
  693. {
  694. foreach(var row in rows)
  695. {
  696. var multFactor = row.Get<JobMaterial, double>(x => x.Dimensions.Value);
  697. row.Update<JobMaterial, double>(x => x.BillOfMaterials, x => x * multFactor);
  698. row.Update<JobMaterial, double>(x => x.Requisitions, x => x * multFactor);
  699. row.Update<JobMaterial, double>(x => x.PickingLists, x => x * multFactor);
  700. row.Update<JobMaterial, double>(x => x.Issued, x => x * multFactor);
  701. row.Update<JobMaterial, double>(x => x.ReservedStock, x => x * multFactor);
  702. row.Update<JobMaterial, double>(x => x.OnOrder, x => x * multFactor);
  703. row.Update<JobMaterial, double>(x => x.JobShortage, x => x * multFactor);
  704. row.Update<JobMaterial, double>(x => x.FreeOnHand, x => x * multFactor);
  705. row.Update<JobMaterial, double>(x => x.FreeOnOrder, x => x * multFactor);
  706. row.Update<JobMaterial, double>(x => x.FreeStockTotal, x => x * multFactor);
  707. row.Update<JobMaterial, double>(x => x.FreeStockShortage, x => x * multFactor);
  708. }
  709. }
  710. CoreRow newrow = table.NewRow();
  711. newrow.LoadValues(rows.First().Values);
  712. var bom = Aggregate(rows, columns, hasStyle, true, x => x.BillOfMaterials, newrow,
  713. x => x.BillOfMaterials);
  714. var requi = Aggregate(rows, columns, hasStyle, true, x => x.Requisitions, newrow, x => x.Requisitions);
  715. var picklist = Aggregate(rows, columns, hasStyle, true, x => x.PickingLists, newrow,
  716. x => x.PickingLists);
  717. var issued = Aggregate(rows, columns, hasStyle, true, x => x.Issued, newrow, x => x.Issued);
  718. var reserved = Aggregate(rows, columns, hasStyle, true, x => x.ReservedStock, newrow,
  719. x => x.ReservedStock);
  720. var ordered = Aggregate(rows, columns, hasStyle, true, x => x.OnOrder, newrow, x => x.OnOrder);
  721. var shortage = Math.Max(0, Math.Max(0, (requi - issued)) - (reserved + ordered));
  722. newrow.Set<JobMaterial, double>(x => x.JobShortage, shortage);
  723. var freestockrows = GetRows(freestock.Rows, freestockcolumns, null, key,
  724. IncludeReserves ? null : (r) => !r.Get<StockHolding, bool>(x => x.Job.JobStatus.Active));
  725. var freeonhand = Aggregate(freestockrows, freestockcolumns, hasStyle, false, x => x.Units, newrow,
  726. x => x.FreeOnHand);
  727. newrow.Set<JobMaterial, double>(x => x.FreeOnHand, freeonhand);
  728. var freeorderrows = GetAllocationRows(freeorders.Rows, freeordercolumns, null, key,
  729. IncludeReserves ? null : (r) => !r.Get<PurchaseOrderItemAllocation, bool>(x => x.Job.JobStatus.Active));
  730. var freeonorder = Aggregate(freeorderrows, freeordercolumns, hasStyle, false, x => x.Quantity, newrow,
  731. x => x.FreeOnOrder);
  732. newrow.Set<JobMaterial, double>(x => x.FreeOnOrder, freeonorder);
  733. newrow.Set<JobMaterial, double>(x => x.FreeStockTotal, freeonhand + freeonorder);
  734. newrow.Set<JobMaterial, double>(x => x.FreeStockShortage, Math.Max(0, shortage - (freeonhand + freeonorder)));
  735. table.Rows.Add(newrow);
  736. }
  737. }
  738. }
  739. action?.Invoke(table, null);
  740. }
  741. );
  742. }
  743. protected override bool FilterRecord(CoreRow row)
  744. {
  745. var result = base.FilterRecord(row)
  746. && (
  747. row.Get<JobMaterial, double>(x => x.BillOfMaterials) != 0.0F ||
  748. row.Get<JobMaterial, double>(x => x.Requisitions) != 0.0F ||
  749. row.Get<JobMaterial, double>(x => x.PickingLists) != 0.0F ||
  750. row.Get<JobMaterial, double>(x => x.Issued) != 0.0F ||
  751. row.Get<JobMaterial, double>(x => x.ReservedStock) != 0.0F ||
  752. row.Get<JobMaterial, double>(x => x.OnOrder) != 0.0F
  753. );
  754. if (ShowIssues)
  755. {
  756. result = result && (
  757. row.Get<JobMaterial, double>(x => x.JobShortage) > 0.0F ||
  758. row.Get<JobMaterial, double>(x => x.FreeStockShortage) > 0.0F);
  759. }
  760. return result;
  761. }
  762. #region Create PO
  763. private bool CreatePO(System.Windows.Controls.Button btn, CoreRow[] rows)
  764. {
  765. if (!rows.Any())
  766. {
  767. MessageBox.Show("Please select at least one row to add to PO");
  768. return false;
  769. }
  770. PurchaseOrder purchaseOrder = new PurchaseOrder();
  771. purchaseOrder.Description = "Created from Job Summary Screen" + System.Environment.NewLine;
  772. purchaseOrder.RaisedBy.ID = empID;
  773. var page = new SupplierPurchaseOrders();
  774. page.OnAfterSave += (form, items) =>
  775. {
  776. PurchaseOrderOnSave(form, items.Cast<PurchaseOrder>().ToArray());
  777. MessageBox.Show("Success - New Purchase Order Created (" + purchaseOrder.PONumber + ")");
  778. };
  779. return page.EditItems(new[] { purchaseOrder }, LoadPurchaseOrderItems, true);
  780. }
  781. private CoreTable LoadPurchaseOrderItems(Type arg)
  782. {
  783. Progress.Show("Working");
  784. var result = new CoreTable();
  785. result.LoadColumns(typeof(PurchaseOrderItem));
  786. List<PurchaseOrderItem> items = new List<PurchaseOrderItem>();
  787. foreach (CoreRow row in SelectedRows)
  788. {
  789. JobMaterial material = row.ToObject<JobMaterial>();
  790. PurchaseOrderItem POItem = new PurchaseOrderItem();
  791. POItem.Product.ID = material.Product.ID;
  792. POItem.Product.Code = material.Product.Code;
  793. POItem.Product.Name = material.Product.Name;
  794. POItem.Description = material.Product.Name;
  795. POItem.Qty = 0;
  796. POItem.Dimensions.CopyFrom(material.Dimensions);
  797. POItem.Style.ID = material.Style.ID;
  798. POItem.Style.Code = material.Style.Code;
  799. POItem.Style.Description = material.Style.Description;
  800. POItem.Dimensions.UnitSize = material.Dimensions.UnitSize;
  801. items.Add(POItem);
  802. }
  803. result.LoadRows(items);
  804. Progress.Close();
  805. return result;
  806. }
  807. private void PurchaseOrderOnSave(IDynamicEditorForm form, PurchaseOrder[] items)
  808. {
  809. Progress.ShowModal("Working", progress =>
  810. {
  811. var poItems = new Client<PurchaseOrderItem>().Query(
  812. new Filter<PurchaseOrderItem>(x => x.PurchaseOrderLink.ID).InList(items.Select(x=>x.ID).ToArray()),
  813. Columns.None<PurchaseOrderItem>()
  814. .Add(x => x.ID)
  815. .Add(x => x.Product.ID)
  816. .Add(x => x.Qty)
  817. .Add(x => x.Dimensions.UnitSize)
  818. .Add(x => x.Dimensions.Value)
  819. .Add(x => x.Style.ID)
  820. .Add(x => x.DueDate)
  821. ).Rows.ToObjects<PurchaseOrderItem>().ToArray();
  822. if (poItems.Any())
  823. {
  824. List<PurchaseOrderItemAllocation> poias = new();
  825. var jobID = Master?.ID ?? Guid.Empty;
  826. if(jobID != Guid.Empty)
  827. {
  828. foreach (var poItem in poItems)
  829. {
  830. var poia = new PurchaseOrderItemAllocation();
  831. poia.Item.ID = poItem.ID;
  832. poia.Job.ID = jobID;
  833. poia.Quantity = poItem.Qty;
  834. poias.Add(poia);
  835. }
  836. }
  837. if (poias.Any())
  838. Client.Save(poias, "Updated on Create Purchase Order from BOM Dashboard");
  839. }
  840. });
  841. }
  842. #endregion
  843. }