RequisitionItemEditor.xaml.cs 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel;
  8. using System.Linq;
  9. using System.Runtime.CompilerServices;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. namespace PRSDesktop.Panels.Requisitions;
  13. /// <summary>
  14. /// Interaction logic for RequisitionItemEditor.xaml
  15. /// </summary>
  16. public partial class RequisitionItemEditor : INotifyPropertyChanged
  17. {
  18. private readonly RequisitionItem Item;
  19. public RequisitionItem[]? Result { get; private set; }
  20. private bool _loadedAllocations;
  21. private bool _loadedStockHoldings;
  22. private bool _loadedProductInstances;
  23. private bool _loadedBOMItems;
  24. private bool _loadedJobRequisitionItems;
  25. private bool _loadedCustom;
  26. private bool _allowActual;
  27. private bool _canSave;
  28. public bool CanSave
  29. {
  30. get => _canSave;
  31. private set
  32. {
  33. _canSave = value;
  34. OnPropertyChanged();
  35. }
  36. }
  37. public RequisitionItemEditor(Func<List<Product>> products, RequisitionItem requisitionItem, bool allowActual)
  38. {
  39. _allowActual = allowActual;
  40. Item = requisitionItem;
  41. InitializeComponent();
  42. StockHoldingProducts.ProductsFunction = products;
  43. ProductInstancesProducts.ProductsFunction = products;
  44. BOMItems.Item = requisitionItem;
  45. JobRequisitionItemsGrid.Item = requisitionItem;
  46. AllocationsPage.Visibility = _allowActual && requisitionItem.Product.ID != Guid.Empty
  47. ? Visibility.Visible
  48. : Visibility.Collapsed;
  49. HoldingsPage.Visibility = _allowActual && requisitionItem.SourceJRI.ID == Guid.Empty ? Visibility.Visible : Visibility.Collapsed;
  50. InstancesPage.Visibility = _allowActual ? Visibility.Collapsed : Visibility.Visible;
  51. BOMPage.Visibility = _allowActual
  52. ? Visibility.Collapsed
  53. : Visibility.Visible;
  54. RequisitionPage.Visibility = _allowActual
  55. ? Visibility.Collapsed
  56. : Visibility.Visible;
  57. TabControl.SelectedItem = requisitionItem.EditType == RequisitionItemEditType.Normal
  58. ? _allowActual
  59. ? requisitionItem.Product.ID != Guid.Empty
  60. ? AllocationsPage
  61. : HoldingsPage
  62. : requisitionItem.JobRequisitionItem.ID == Guid.Empty
  63. ? BOMPage
  64. : RequisitionPage
  65. : CustomPage;
  66. CanSave = false;
  67. UpdateState();
  68. }
  69. private void UpdateState()
  70. {
  71. if (TabControl.SelectedTab == AllocationsPage)
  72. {
  73. if (!_loadedAllocations)
  74. {
  75. LoadAllocations();
  76. _loadedAllocations = true;
  77. }
  78. }
  79. else if(TabControl.SelectedTab == HoldingsPage)
  80. {
  81. if (!_loadedStockHoldings)
  82. {
  83. LoadStockHoldings();
  84. _loadedStockHoldings = true;
  85. }
  86. }
  87. else if(TabControl.SelectedTab == BOMPage)
  88. {
  89. if (!_loadedBOMItems)
  90. {
  91. LoadBOMItems();
  92. _loadedBOMItems = true;
  93. }
  94. }
  95. else if(TabControl.SelectedTab == RequisitionPage)
  96. {
  97. if (!_loadedJobRequisitionItems)
  98. {
  99. LoadJobRequisitionItems();
  100. _loadedJobRequisitionItems = true;
  101. }
  102. }
  103. else if(TabControl.SelectedTab == InstancesPage)
  104. {
  105. if (!_loadedProductInstances)
  106. {
  107. LoadProductInstances();
  108. _loadedProductInstances = true;
  109. }
  110. }
  111. else if (TabControl.SelectedTab == CustomPage)
  112. {
  113. if (!_loadedCustom)
  114. {
  115. LoadCustom();
  116. _loadedCustom = true;
  117. }
  118. }
  119. CheckCanSave();
  120. }
  121. private void CheckCanSave()
  122. {
  123. if (TabControl.SelectedTab == AllocationsPage)
  124. CanSave = CanSaveAllocations();
  125. else if (TabControl.SelectedTab == HoldingsPage)
  126. CanSave = CanSaveStockHoldings();
  127. else if (TabControl.SelectedTab == BOMPage)
  128. CanSave = CanSaveBOMItems();
  129. else if (TabControl.SelectedTab == RequisitionPage)
  130. CanSave = CanSaveJobRequisitionItems();
  131. else if (TabControl.SelectedTab == InstancesPage)
  132. CanSave = CanSaveProductInstances();
  133. else if (TabControl.SelectedTab == CustomPage)
  134. CanSave = true;
  135. }
  136. private void DoSave()
  137. {
  138. if (TabControl.SelectedTab == AllocationsPage)
  139. SaveAllocations();
  140. else if (TabControl.SelectedTab == HoldingsPage)
  141. SaveStockHoldings();
  142. else if (TabControl.SelectedTab == BOMPage)
  143. SaveBOMItems();
  144. else if (TabControl.SelectedTab == RequisitionPage)
  145. SaveJobRequisitionItems();
  146. else if (TabControl.SelectedTab == InstancesPage)
  147. SaveProductInstances();
  148. else if (TabControl.SelectedTab == CustomPage)
  149. SaveCustom();
  150. }
  151. private void OKButton_Click(object sender, RoutedEventArgs e)
  152. {
  153. DoSave();
  154. DialogResult = true;
  155. Close();
  156. }
  157. private void CancelButton_Click(object sender, RoutedEventArgs e)
  158. {
  159. Item.CancelChanges();
  160. DialogResult = false;
  161. Close();
  162. }
  163. private void TabChanged(object sender, SelectionChangedEventArgs e)
  164. {
  165. if (e.OriginalSource == sender)
  166. UpdateState();
  167. }
  168. #region Allocations
  169. private IEnumerable<StockSelectionItem> CreateStockSelectionItems(IEnumerable<IGrouping<Tuple<Guid,Guid>, StockMovement>> groups, Tuple<Guid,Guid,double>[] values, Func<StockMovement,string> description)
  170. {
  171. var _result = new List<StockSelectionItem>();
  172. foreach (var _group in groups)
  173. {
  174. var _units = _group.Sum(x => x.Units);
  175. if (!_units.IsEffectivelyEqual(0.0))
  176. {
  177. var _holding = new StockHolding();
  178. _holding.Location.ID = _group.Key.Item1;
  179. _holding.Job.ID = _group.Key.Item2;
  180. _holding.Location.Synchronise(_group.FirstOrDefault()?.Location ?? new StockLocationLink());
  181. _holding.Product.ID = Item.Product.ID;
  182. _holding.Product.Synchronise(Item.Product);
  183. _holding.Style.ID = Item.Style.ID;
  184. _holding.Style.Synchronise(Item.Style);
  185. _holding.Job.CopyFrom(_group.FirstOrDefault()?.Job ?? new JobLink());
  186. _holding.Dimensions.CopyFrom(Item.Dimensions);
  187. _holding.Location.Area.Code = description(_group.FirstOrDefault() ?? new StockMovement());
  188. _holding.Units = _units;
  189. _holding.AverageValue = values.FirstOrDefault(x=>x.Item1 == _group.Key.Item1 && x.Item2 == _group.Key.Item2)?.Item3 ?? 0.0;
  190. var _jri = new JobRequisitionItem()
  191. {
  192. ID = _group.FirstOrDefault()?.JobRequisitionItem.ID ?? Guid.Empty
  193. };
  194. _result.Add(new StockSelectionItem() { Holding = _holding, MaxValue = Item.Quantity, JRI = _jri });
  195. }
  196. }
  197. return _result;
  198. }
  199. private void LoadAllocations()
  200. {
  201. MultiQuery query = new MultiQuery();
  202. query.Add(new Filter<StockMovement>(x=>x.Product.ID).IsEqualTo(Item.Product.ID)
  203. .And(x=>x.Style.ID).IsEqualTo(Item.Style.ID)
  204. .And(x=>x.Dimensions).DimensionEquals(Item.Dimensions)
  205. .And(
  206. new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Item.JobRequisitionItem.ID)
  207. .Or(new Filter<StockMovement>(x => x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID)
  208. .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty))
  209. .Or(new Filter<StockMovement>(x => x.Job.ID).IsEqualTo(Guid.Empty)
  210. .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty))
  211. ),
  212. Columns.None<StockMovement>()
  213. .Add(x => x.Location.ID)
  214. .Add(x => x.Location.Code)
  215. .Add(x => x.Location.Description)
  216. .Add(x => x.Job.ID)
  217. .Add(x=>x.Job.JobNumber)
  218. .Add(x=>x.Job.Name)
  219. .Add(x => x.JobRequisitionItem.ID)
  220. .Add(x=>x.JobRequisitionItem.Requisition.Number)
  221. .Add(x=>x.JobRequisitionItem.Requisition.Description)
  222. .Add(x => x.Units)
  223. );
  224. query.Add(new Filter<StockHolding>(x=>x.Product.ID).IsEqualTo(Item.Product.ID)
  225. .And(x=>x.Style.ID).IsEqualTo(Item.Style.ID)
  226. .And(x=>x.Dimensions).DimensionEquals(Item.Dimensions),
  227. Columns.None<StockHolding>()
  228. .Add(x => x.Location.ID)
  229. .Add(x => x.Job.ID)
  230. .Add(x => x.AverageValue)
  231. );
  232. query.Query();
  233. var values = query.Get<StockHolding>().ToTuples<StockHolding,Guid,Guid,double>(x=>x.Location.ID, x=>x.Job.ID, x=>x.AverageValue).ToArray();
  234. var movements = query.Get<StockMovement>().ToArray<StockMovement>();
  235. List<StockSelectionItem> items = new();
  236. var allocations = movements.Where(x => x.JobRequisitionItem.ID == Item.JobRequisitionItem.ID)
  237. .GroupBy(x => new Tuple<Guid,Guid>(x.Location.ID,x.Job.ID));
  238. items.AddRange(
  239. CreateStockSelectionItems(
  240. allocations,
  241. values,
  242. (m) => $"{m.Job.JobNumber}:{m.JobRequisitionItem.Requisition.Number} {m.JobRequisitionItem.Requisition.Description}"
  243. )
  244. );
  245. // if the pickinglist does not have a job, skip this bit
  246. var reserves = movements.Where(x =>
  247. x.JobRequisitionItem.ID != Guid.Empty && x.Job.ID == Item.RequisitionLink.JobLink.ID &&
  248. x.JobRequisitionItem.ID == Guid.Empty)
  249. .GroupBy(x => new Tuple<Guid,Guid>(x.Location.ID, x.Job.ID));
  250. items.AddRange(
  251. CreateStockSelectionItems(
  252. reserves,
  253. values,
  254. (m) => $"{m.Job.JobNumber} (Unallocated Stock)"
  255. )
  256. );
  257. var freeitems = movements.Where(x => x.Job.ID == Guid.Empty && x.JobRequisitionItem.ID == Guid.Empty)
  258. .GroupBy(x => new Tuple<Guid,Guid>(x.Location.ID,x.Job.ID));
  259. items.AddRange(
  260. CreateStockSelectionItems(
  261. freeitems,
  262. values,
  263. (m) => $"General Stock"
  264. )
  265. );
  266. Allocations.Items = items;
  267. Allocations.Refresh(true,true);
  268. }
  269. private void Allocations_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
  270. {
  271. UpdateMaximums();
  272. }
  273. private void UpdateMaximums()
  274. {
  275. foreach (var item in Allocations.Items)
  276. item.MaxValue = Item.Quantity + item.Issued - Allocations.Items.Sum(x=>x.Issued);
  277. }
  278. private void Allocations_OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
  279. {
  280. UpdateMaximums();
  281. CheckCanSave();
  282. }
  283. private bool CanSaveAllocations()
  284. {
  285. return Allocations.Items.Sum(x=>x.Issued).IsEffectivelyGreaterThan(0.0);
  286. }
  287. private RequisitionItem CopyRequisitionItem()
  288. {
  289. var newItem = new RequisitionItem();
  290. newItem.Done = Item.Done;
  291. newItem.Image.CopyFrom(Item.Image);
  292. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  293. newItem.SourceJRI.ID = Item.SourceJRI.ID;
  294. newItem.Product.CopyFrom(Item.Product);
  295. newItem.Style.CopyFrom(Item.Style);
  296. newItem.Location.CopyFrom(Item.Location);
  297. newItem.JobRequisitionItem.ID = Item.JobRequisitionItem.ID;
  298. newItem.Dimensions.CopyFrom(Item.Dimensions, true);
  299. newItem.JobLink.CopyFrom(Item.JobLink);
  300. newItem.Description = Item.Description;
  301. newItem.Quantity = Item.Quantity;
  302. newItem.Cost = Item.Cost;
  303. newItem.EditType = RequisitionItemEditType.Normal;
  304. return newItem;
  305. }
  306. private void SaveAllocations()
  307. {
  308. var remainder = Item.Quantity;
  309. var newItems = new List<RequisitionItem>();
  310. foreach(var item in Allocations.Items.Where(x=>!x.Issued.IsEffectivelyEqual(0.0)))
  311. {
  312. var newItem = newItems.Count == 0 ? Item : CopyRequisitionItem();
  313. newItem.Location.CopyFrom(item.Holding.Location);
  314. newItem.Cost = item.Holding.AverageValue;
  315. newItem.SourceJRI.CopyFrom(item.JRI);
  316. newItem.JobLink.CopyFrom(item.Holding.Job);
  317. // Automagically attach to Requisition if it seems appropriate
  318. // ie selected from a JRI allocation, and current source jri is blank
  319. if (newItem.JobRequisitionItem.ID == Guid.Empty
  320. && newItem.RequisitionLink.JobLink.ID == item.Holding.Job.ID
  321. && item.JRI.ID != Guid.Empty)
  322. newItem.JobRequisitionItem.ID = item.JRI.ID;
  323. newItem.Quantity = item.Issued;
  324. newItem.ActualQuantity = item.Issued;
  325. newItem.Cost = item.Holding.AverageValue;
  326. newItem.Done = true;
  327. remainder -= item.Issued;
  328. newItems.Add(newItem);
  329. }
  330. if(!remainder.IsEffectivelyEqual(0.0))
  331. {
  332. var newItem = CopyRequisitionItem();
  333. newItem.Location.CopyFrom(new StockLocationLink());
  334. newItem.SourceJRI.CopyFrom(new JobRequisitionItemLink());
  335. newItem.JobLink.CopyFrom(new JobLink());
  336. newItem.Quantity = remainder;
  337. newItem.Done = false;
  338. newItems.Add(newItem);
  339. }
  340. Client.Save(newItems, "Updated from RequisitionItemEditor(Allocations)");
  341. Result = newItems.ToArray();
  342. }
  343. #endregion
  344. #region StockHoldings
  345. private void LoadStockHoldings()
  346. {
  347. StockHoldingProducts.Refresh(true, true);
  348. foreach(var column in StockHolding.Columns)
  349. {
  350. StockHoldings.HiddenColumns.Add(column);
  351. }
  352. foreach(var column in Columns.None<StockHolding>()
  353. .AddSubColumns(x => x.Product, LookupFactory.DefineLookupColumns<RequisitionItem, Product, ProductLink>(x => x.Product).CastIntersection<ProductLink>())
  354. .AddSubColumns(x => x.Style, LookupFactory.DefineLookupColumns<RequisitionItem, ProductStyle, ProductStyleLink>(x => x.Style).CastIntersection<ProductStyleLink>())
  355. .AddSubColumns(x => x.Location, LookupFactory.DefineLookupColumns<RequisitionItem, StockLocation, StockLocationLink>(x => x.Location).CastIntersection<StockLocationLink>())
  356. .AddSubColumns(x => x.Job, LookupFactory.DefineLookupColumns<RequisitionItem, Job, JobLink>(x => x.JobLink).CastIntersection<JobLink>())
  357. .Add(x=>x.AverageValue))
  358. {
  359. StockHoldings.HiddenColumns.Add(column);
  360. }
  361. StockHoldings.HiddenColumns.Add(x => x.Available);
  362. StockHoldings.Refresh(true, false);
  363. if(Item.Product.ID != Guid.Empty)
  364. {
  365. // Delaying, cause otherwise it doesn't work.
  366. Dispatcher.BeginInvoke(() =>
  367. {
  368. StockHoldingProducts.SelectedRows = StockHoldingProducts.Data.Rows.Where(x => x.Get<Product, Guid>(x => x.ID) == Item.Product.ID).ToArray();
  369. StockHoldingProducts.ScrollIntoView(StockHoldingProducts.SelectedRows[0]);
  370. });
  371. }
  372. }
  373. private void StockHoldingProducts_OnSelectItem(object sender, InABox.DynamicGrid.DynamicGridSelectionEventArgs e)
  374. {
  375. StockHoldings.Product = e.Rows is not null && e.Rows.Length > 0 ? e.Rows[0].ToObject<Product>() : null;
  376. StockHoldings.Refresh(false, true);
  377. CheckCanSave();
  378. }
  379. private void StockHoldings_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
  380. {
  381. var row = StockHoldings.Data.Rows.FirstOrDefault();
  382. if (row != null)
  383. {
  384. StockHoldings.SelectedRows = new CoreRow[] { row };
  385. SelectHolding(new CoreRow[] { row });
  386. }
  387. }
  388. private void StockHoldings_OnSelectItem(object sender, InABox.DynamicGrid.DynamicGridSelectionEventArgs e)
  389. {
  390. SelectHolding(e.Rows);
  391. }
  392. private void SelectHolding(CoreRow[]? rows)
  393. {
  394. StockHolding = rows?.FirstOrDefault()?.ToObject<StockHolding>();
  395. RefreshJobRequisitionItems();
  396. JRIRow.Height = _allowActual && StockHolding is not null ? new GridLength(1, GridUnitType.Star) : new GridLength(0);
  397. TotalRow.Height = _allowActual && StockHolding is not null ? new GridLength(40) : new GridLength(0);
  398. RequiredEditor.SetValue(Grid.ColumnProperty, _allowActual && StockHolding is not null ? 4 : 6);
  399. CheckCanSave();
  400. }
  401. private bool CanSaveStockHoldings()
  402. {
  403. return StockHolding is not null && TotalTaken > 0;
  404. }
  405. private void SaveStockHoldings()
  406. {
  407. if (StockHolding is null) return;
  408. var items = JRIItems.Where(x => x.Taken > 0).ToArray();
  409. if (items.Length == 0) return;
  410. Item.Product.CopyFrom(StockHolding.Product);
  411. Item.Style.CopyFrom(StockHolding.Style);
  412. Item.Location.CopyFrom(StockHolding.Location);
  413. Item.Dimensions.CopyFrom(StockHolding.Dimensions, true);
  414. Item.JobLink.CopyFrom(StockHolding.Job);
  415. Item.Cost = StockHolding.AverageValue;
  416. Item.EditType = RequisitionItemEditType.Normal;
  417. var remainder = Item.Quantity;
  418. var newItems = new List<RequisitionItem>();
  419. foreach(var item in items)
  420. {
  421. RequisitionItem newItem;
  422. if(newItems.Count == 0)
  423. {
  424. newItem = Item;
  425. }
  426. else
  427. {
  428. newItem = new RequisitionItem();
  429. newItem.Done = Item.Done;
  430. newItem.Description = Item.Description;
  431. newItem.Image.CopyFrom(Item.Image);
  432. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  433. newItem.JobRequisitionItem.CopyFrom(Item.JobRequisitionItem);
  434. newItem.Product.CopyFrom(Item.Product);
  435. newItem.Style.CopyFrom(Item.Style);
  436. newItem.Location.CopyFrom(Item.Location);
  437. newItem.Dimensions.CopyFrom(Item.Dimensions, true);
  438. newItem.JobLink.CopyFrom(Item.JobLink);
  439. newItem.EditType = RequisitionItemEditType.Normal;
  440. }
  441. // Automagically attach to Requisition if it seems appropriate
  442. // ie selected from a JRI allocation, and current source jri is blank
  443. if (newItem.JobRequisitionItem.ID == Guid.Empty
  444. && newItem.RequisitionLink.JobLink.ID == StockHolding.Job.ID
  445. && item.ID != Guid.Empty)
  446. newItem.JobRequisitionItem.ID = item.ID;
  447. newItem.SourceJRI.ID = item.ID;
  448. newItem.Quantity = item.Taken;
  449. newItem.ActualQuantity = item.Taken;
  450. newItem.Cost = StockHolding.AverageValue;
  451. newItem.Done = true;
  452. remainder -= item.Taken;
  453. newItems.Add(newItem);
  454. }
  455. if(remainder > 0)
  456. {
  457. var newItem = new RequisitionItem();
  458. newItem.Done = false;
  459. newItem.Description = Item.Description;
  460. newItem.Image.CopyFrom(Item.Image);
  461. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  462. newItem.JobRequisitionItem.CopyFrom(Item.JobRequisitionItem);
  463. newItem.SourceJRI.ID = Guid.Empty;
  464. newItem.Product.CopyFrom(Item.Product);
  465. newItem.Style.CopyFrom(Item.Style);
  466. newItem.Dimensions.CopyFrom(Item.Dimensions, true);
  467. newItem.Quantity = remainder;
  468. newItems.Add(newItem);
  469. }
  470. Client.Save(newItems, "Edited by user.");
  471. Result = newItems.ToArray();
  472. }
  473. #region JobRequisitionItem Selection
  474. public event PropertyChangedEventHandler? PropertyChanged;
  475. protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
  476. {
  477. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  478. }
  479. private bool _observing = false;
  480. public void SetObserving(bool observing)
  481. {
  482. if(_observing != observing)
  483. {
  484. _observing = observing;
  485. if (_observing)
  486. {
  487. Recalculate();
  488. }
  489. }
  490. }
  491. private StockHolding? StockHolding;
  492. private List<RequisitionItemStockHoldingItem> _jriItems;
  493. public List<RequisitionItemStockHoldingItem> JRIItems
  494. {
  495. get => _jriItems;
  496. set
  497. {
  498. _jriItems = value;
  499. OnPropertyChanged();
  500. }
  501. }
  502. private void RefreshJobRequisitionItems()
  503. {
  504. SetObserving(false);
  505. if(StockHolding is null)
  506. {
  507. JRIItems = [];
  508. }
  509. else
  510. {
  511. var jris = StockHolding.LoadRequisitionItems(true);
  512. JRIItems = jris.Select(x =>
  513. {
  514. var newItem = new RequisitionItemStockHoldingItem
  515. {
  516. Quantity = x.Qty,
  517. Taken = 0,
  518. ID = x.ID,
  519. Text = x.ID == Guid.Empty
  520. ? "General Stock"
  521. : $"{x.Job.JobNumber}:{x.Requisition.Number} {x.Requisition.Description} ({x.Qty})",
  522. };
  523. newItem.PropertyChanged += (o, e) => Recalculate();
  524. return newItem;
  525. }).ToList();
  526. }
  527. SetObserving(true);
  528. }
  529. private void Recalculate()
  530. {
  531. if (!_observing) return;
  532. TotalTaken = JRIItems.Sum(x => x.Taken);
  533. }
  534. private double _totalTaken;
  535. public double TotalTaken
  536. {
  537. get => _totalTaken;
  538. set
  539. {
  540. _totalTaken = value;
  541. OnPropertyChanged();
  542. CheckCanSave();
  543. }
  544. }
  545. public double RequiredQuantity
  546. {
  547. get => Item.Quantity;
  548. set
  549. {
  550. Item.Quantity = value;
  551. CheckCanSave();
  552. OnPropertyChanged();
  553. }
  554. }
  555. private void None_Click(object sender, RoutedEventArgs e)
  556. {
  557. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  558. item.Taken = 0;
  559. }
  560. private void Minus_Click(object sender, RoutedEventArgs e)
  561. {
  562. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  563. item.Taken = Math.Max(0, item.Taken - 1);
  564. }
  565. private void Plus_Click(object sender, RoutedEventArgs e)
  566. {
  567. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  568. item.Taken = Math.Min(item.Taken + 1, item.Quantity);
  569. }
  570. private void All_Click(object sender, RoutedEventArgs e)
  571. {
  572. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  573. item.Taken = Math.Min(Math.Max(RequiredQuantity - (TotalTaken - item.Taken), 0), item.Quantity);
  574. }
  575. #endregion
  576. #endregion
  577. #region BOM Items
  578. private void LoadBOMItems()
  579. {
  580. BOMItems.Refresh(true,true);
  581. }
  582. private bool CanSaveBOMItems()
  583. {
  584. return BOMItems.Data.Rows.Any(r =>!r.Get<JobBOMSummary,double>(x=>x.Quantity).IsEffectivelyEqual(0.0));
  585. }
  586. private void SaveBOMItems()
  587. {
  588. var result = new List<RequisitionItem>() { Item };
  589. var bomItems = BOMItems.Data.Rows
  590. .Where(r => !r.Get<JobBOMSummary, double>(x => x.Quantity).IsEffectivelyEqual(0.0))
  591. .Select(r => r.ToObject<JobBOMSummary>()).ToArray();
  592. var first = bomItems.FirstOrDefault();
  593. if (first is null)
  594. return;
  595. Item.Product.CopyFrom(first.Product);
  596. Item.Style.CopyFrom(first.Style);
  597. Item.Dimensions.CopyFrom(first.Dimensions, true);
  598. Item.EditType = RequisitionItemEditType.Normal;
  599. Item.Quantity = first.Quantity;
  600. foreach (var item in bomItems.Skip(1).ToArray())
  601. {
  602. var newItem = new RequisitionItem();
  603. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  604. newItem.Done = false;
  605. newItem.Description = item.Product.Name;
  606. newItem.Product.CopyFrom(item.Product);
  607. newItem.Style.CopyFrom(item.Style);
  608. newItem.Dimensions.CopyFrom(item.Dimensions, true);
  609. newItem.EditType = RequisitionItemEditType.Normal;
  610. newItem.Quantity = item.Quantity;
  611. result.Add(newItem);
  612. }
  613. Client.Save(result, "Edited by user.");
  614. Result = result.ToArray();
  615. }
  616. private void BOMItems_OnOnChanged(object? sender, EventArgs e)
  617. {
  618. CheckCanSave();
  619. }
  620. #endregion
  621. #region Job Requisition Items
  622. private void LoadJobRequisitionItems()
  623. {
  624. JobRequisitionItemsGrid.Refresh(true,true);
  625. }
  626. private bool CanSaveJobRequisitionItems()
  627. {
  628. return JobRequisitionItemsGrid.Data.Rows.Any(r =>!r.Get<JobJRISummary,double>(x=>x.Quantity).IsEffectivelyEqual(0.0));
  629. }
  630. private void SaveJobRequisitionItems()
  631. {
  632. var result = new List<RequisitionItem>() { Item };
  633. var items = JobRequisitionItemsGrid.Data.Rows
  634. .Where(r => !r.Get<JobJRISummary, double>(x => x.Quantity).IsEffectivelyEqual(0.0))
  635. .Select(r => r.ToObject<JobJRISummary>()).ToArray();
  636. var first = items.FirstOrDefault();
  637. if (first is null)
  638. return;
  639. Item.Product.CopyFrom(first.JRI.Product);
  640. Item.Style.CopyFrom(first.JRI.Style);
  641. Item.Dimensions.CopyFrom(first.JRI.Dimensions, true);
  642. Item.JobRequisitionItem.CopyFrom(first.JRI);
  643. Item.EditType = RequisitionItemEditType.Normal;
  644. Item.Quantity = first.Quantity;
  645. foreach (var item in items.Skip(1).ToArray())
  646. {
  647. var newItem = new RequisitionItem();
  648. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  649. newItem.Done = false;
  650. newItem.Description = item.JRI.Product.Name;
  651. newItem.Product.CopyFrom(item.JRI.Product);
  652. newItem.Style.CopyFrom(item.JRI.Style);
  653. newItem.Dimensions.CopyFrom(item.JRI.Dimensions, true);
  654. newItem.JobRequisitionItem.CopyFrom(item.JRI);
  655. newItem.EditType = RequisitionItemEditType.Normal;
  656. newItem.Quantity = item.Quantity;
  657. result.Add(newItem);
  658. }
  659. Client.Save(result, "Edited by user.");
  660. Result = result.ToArray();
  661. }
  662. private void JobRequisitionItems_OnOnChanged(object? sender, EventArgs e)
  663. {
  664. CheckCanSave();
  665. }
  666. #endregion
  667. #region Product Instances
  668. private ProductInstance? SelectedProductInstance;
  669. private void LoadProductInstances()
  670. {
  671. ProductInstancesProducts.Refresh(true, true);
  672. ProductInstances.Refresh(true, false);
  673. foreach(var column in Columns.None<ProductInstance>().Add(x => x.Style.ID).AddDimensionsColumns(x => x.Dimensions))
  674. {
  675. ProductInstances.HiddenColumns.Add(column);
  676. }
  677. if(Item.Product.ID != Guid.Empty)
  678. {
  679. // Delaying, cause otherwise it doesn't work.
  680. Dispatcher.BeginInvoke(() =>
  681. {
  682. ProductInstancesProducts.SelectedRows = ProductInstancesProducts.Data.Rows.Where(x => x.Get<Product, Guid>(x => x.ID) == Item.Product.ID).ToArray();
  683. if (ProductInstancesProducts.SelectedRows.Any())
  684. ProductInstancesProducts.ScrollIntoView(ProductInstancesProducts.SelectedRows[0]);
  685. });
  686. }
  687. }
  688. private bool CanSaveProductInstances()
  689. {
  690. return ProductInstances.Product is not null && SelectedProductInstance is not null && RequiredQuantity > 0;
  691. }
  692. private void SaveProductInstances()
  693. {
  694. if (ProductInstances.Product is null || SelectedProductInstance is null) return;
  695. Item.Product.CopyFrom(ProductInstances.Product);
  696. Item.Style.CopyFrom(SelectedProductInstance.Style);
  697. Item.Dimensions.CopyFrom(SelectedProductInstance.Dimensions, true);
  698. Item.Location.ID = Guid.Empty;
  699. Item.EditType = RequisitionItemEditType.Normal;
  700. Client.Save(Item, "Edited by user.");
  701. Result = [Item];
  702. }
  703. private void ProductInstancesProducts_OnSelectItem(object sender, InABox.DynamicGrid.DynamicGridSelectionEventArgs e)
  704. {
  705. ProductInstances.Product = e.Rows is not null && e.Rows.Length > 0 ? e.Rows[0].ToObject<Product>() : null;
  706. ProductInstances.Refresh(false, true);
  707. CheckCanSave();
  708. }
  709. private void ProductInstances_OnSelectItem(object sender, DynamicGridSelectionEventArgs e)
  710. {
  711. SelectedProductInstance = e.Rows is not null && e.Rows.Length > 0 ? e.Rows[0].ToObject<ProductInstance>() : null;
  712. CheckCanSave();
  713. }
  714. #endregion
  715. #region Custom
  716. private void LoadCustom()
  717. {
  718. CustomEditorGrid.Setup(typeof(RequisitionItem));
  719. CustomEditorGrid.Items = new RequisitionItem[] { Item };
  720. }
  721. private static readonly Columns<RequisitionItem> ActualCustomColumns = Columns.None<RequisitionItem>()
  722. .Add(x => x.Product.ID)
  723. .Add(x => x.Style.ID)
  724. .Add(x => x.Dimensions)
  725. .Add(x => x.Description)
  726. .Add(x => x.Location.ID)
  727. .Add(x => x.Quantity)
  728. .Add(x => x.ActualQuantity);
  729. private static readonly Columns<RequisitionItem> RequstedCustomColumns = Columns.None<RequisitionItem>()
  730. .Add(x => x.Product.ID)
  731. .Add(x => x.Style.ID)
  732. .Add(x => x.Dimensions)
  733. .Add(x => x.Description)
  734. .Add(x => x.Location.ID)
  735. .Add(x => x.Quantity)
  736. .Add(x => x.ActualQuantity);
  737. // private static readonly Columns<RequisitionItem> ProductInstanceCustomColumns = new Columns<RequisitionItem>()
  738. // .Add(x => x.Product.ID)
  739. // .Add(x => x.Style.ID)
  740. // .Add(x => x.Dimensions)
  741. // .Add(x => x.Description)
  742. // .Add(x => x.Quantity);
  743. private void CustomEditorGrid_OnFormCustomiseEditor(IDynamicEditorForm sender, object items, DynamicGridColumn column, BaseEditor editor)
  744. {
  745. var columns = _allowActual ? ActualCustomColumns : RequstedCustomColumns;
  746. editor.Editable = columns.Contains(column.ColumnName)
  747. ? Editable.Enabled
  748. : Editable.Hidden;
  749. }
  750. /*
  751. private void LoadCustom()
  752. {
  753. var host = new DefaultDynamicEditorHost<RequisitionItem>
  754. {
  755. Items = [Item]
  756. };
  757. var editors = new Dictionary<string, IDynamicEditorControl>();
  758. BaseDynamicEditorControl? CreateControl(Expression<Func<RequisitionItem, object?>> exp, int row)
  759. {
  760. var property = DatabaseSchema.Property(exp)!;
  761. BaseEditor? editor;
  762. if (property.HasParentEditor())
  763. {
  764. property = property.GetParentWithEditor();
  765. }
  766. if (property is null) return null;
  767. editor = property.Editor;
  768. editor = editor.CloneEditor();
  769. var control = DynamicEditorControlFactory.CreateControl(editor, host)!;
  770. Grid.SetRow(control, row);
  771. Grid.SetColumn(control, 1);
  772. control.Margin = new Thickness(5, 2.5, 5, 2.5);
  773. CustomEditorGrid.Children.Add(control);
  774. editors[property.Name] = control;
  775. control.IsEnabled = true;
  776. control.ColumnName = property.Name;
  777. control.Color = editor is UniqueCodeEditor ? Color.FromArgb(0xFF, 0xF6, 0xC9, 0xE8) : Colors.LightYellow;
  778. control.Configure();
  779. control.Loaded = true;
  780. foreach(var (k, v) in control.GetValues())
  781. {
  782. var value = DatabaseSchema.Property(typeof(RequisitionItem), k)!.Getter()(Item);
  783. if(!Equals(v, value))
  784. {
  785. bool bLoaded = control.Loaded;
  786. control.Loaded = false;
  787. control.SetValue(k, value);
  788. control.Loaded = bLoaded;
  789. }
  790. }
  791. control.OnEditorValueChanged += (sender, values) =>
  792. {
  793. var changededitors = new Dictionary<string, object?>();
  794. void ExtractChanged(Dictionary<string, object?>? columns)
  795. {
  796. if (columns != null)
  797. foreach (var (change, value) in columns)
  798. if (!changededitors.ContainsKey(change) && !change.Equals(sender.ColumnName))
  799. changededitors[change] = value;
  800. }
  801. var name = sender.ColumnName;
  802. var resetAll = false;
  803. if(values.Remove(name, out var value))
  804. {
  805. var changedcolumns = DynamicGridUtils.UpdateEditorValue([Item], sender.ColumnName, value);
  806. resetAll = changedcolumns?.ContainsKey(name) ?? false;
  807. ExtractChanged(changedcolumns);
  808. }
  809. foreach(var (k, v) in values)
  810. {
  811. var changes = new Dictionary<string, object?>();
  812. if (resetAll)
  813. {
  814. var prop = DatabaseSchema.Property(typeof(RequisitionItem), k);
  815. if(prop is not null)
  816. {
  817. var def = CoreUtils.GetDefault(prop.PropertyType);
  818. var resetChanges = DynamicGridUtils.UpdateEditorValue([Item], k, def);
  819. if(resetChanges is not null)
  820. {
  821. changes = resetChanges;
  822. }
  823. }
  824. else
  825. {
  826. }
  827. }
  828. var changedOtherColumns = DynamicGridUtils.UpdateEditorValue([Item], k, v);
  829. if(changedOtherColumns is not null)
  830. {
  831. foreach (var (k1, v1) in changedOtherColumns)
  832. {
  833. changes[k1] = v1;
  834. }
  835. }
  836. ExtractChanged(changes);
  837. }
  838. if (changededitors.Count != 0)
  839. {
  840. foreach(var (k, v) in changededitors)
  841. {
  842. var editor = editors.GetValueOrDefault(k) ?? editors.FirstOrDefault(x => k.StartsWith(x.Key + ".")).Value;
  843. if(editor is not null)
  844. {
  845. editor.Loaded = false;
  846. editor.SetValue(k, v);
  847. editor.Loaded = true;
  848. }
  849. }
  850. }
  851. };
  852. return control;
  853. }
  854. var productEditor = CreateControl(x => x.Product.ID, 0);
  855. var styleEditor = CreateControl(x => x.Style.ID, 1);
  856. var dimensionsEditor = CreateControl(x => x.Dimensions.Quantity, 2);
  857. var descriptionEditor = CreateControl(x => x.Description, 3);
  858. var locationEditor = CreateControl(x => x.Location.ID, 4);
  859. var quantityEditor = CreateControl(x => x.Quantity, 5);
  860. }
  861. */
  862. private void SaveCustom()
  863. {
  864. Item.EditType = RequisitionItemEditType.Custom;
  865. Client.Save(Item, "Custom edited by user.");
  866. Result = [Item];
  867. }
  868. #endregion
  869. }