RequisitionItemEditor.xaml.cs 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  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<Guid, StockMovement>> groups, 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;
  179. _holding.Location.Synchronise(_group.FirstOrDefault()?.Location ?? new StockLocationLink());
  180. _holding.Product.ID = Item.Product.ID;
  181. _holding.Product.Synchronise(Item.Product);
  182. _holding.Style.ID = Item.Style.ID;
  183. _holding.Style.Synchronise(Item.Style);
  184. _holding.Dimensions.CopyFrom(Item.Dimensions);
  185. _holding.Location.Area.Code = description(_group.FirstOrDefault() ?? new StockMovement());
  186. _holding.Units = _units;
  187. var _jri = new JobRequisitionItem()
  188. {
  189. ID = _group.FirstOrDefault()?.JobRequisitionItem.ID ?? Guid.Empty
  190. };
  191. _result.Add(new StockSelectionItem() { Holding = _holding, MaxValue = Item.Quantity, JRI = _jri });
  192. }
  193. }
  194. return _result;
  195. }
  196. private void LoadAllocations()
  197. {
  198. var movements = Client.Query(
  199. new Filter<StockMovement>(x=>x.Product.ID).IsEqualTo(Item.Product.ID)
  200. .And(x=>x.Style.ID).IsEqualTo(Item.Style.ID)
  201. .And(x=>x.Dimensions).DimensionEquals(Item.Dimensions)
  202. .And(
  203. new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Item.SourceJRI.ID)
  204. .Or(new Filter<StockMovement>(x => x.Job.ID).IsEqualTo(Item.RequisitionLink.JobLink.ID)
  205. .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty))
  206. .Or(new Filter<StockMovement>(x => x.Job.ID).IsEqualTo(Guid.Empty)
  207. .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty))
  208. ),
  209. Columns.None<StockMovement>()
  210. .Add(x => x.Location.ID)
  211. .Add(x => x.Location.Code)
  212. .Add(x => x.Location.Description)
  213. .Add(x => x.Job.ID)
  214. .Add(x=>x.Job.JobNumber)
  215. .Add(x=>x.Job.Name)
  216. .Add(x => x.JobRequisitionItem.ID)
  217. .Add(x=>x.JobRequisitionItem.Requisition.Number)
  218. .Add(x=>x.JobRequisitionItem.Requisition.Description)
  219. .Add(x => x.Units)
  220. ).Rows.Select(x=>x.ToObject<StockMovement>()).ToArray();
  221. List<StockSelectionItem> items = new();
  222. var allocations = movements.Where(x => x.JobRequisitionItem.ID == Item.SourceJRI.ID)
  223. .GroupBy(x => x.Location.ID);
  224. items.AddRange(
  225. CreateStockSelectionItems(
  226. allocations,
  227. (m) => $"{m.Job.JobNumber}:{m.JobRequisitionItem.Requisition.Number} {m.JobRequisitionItem.Requisition.Description}"
  228. )
  229. );
  230. // if the pickinglist does not have a job, skip this bit
  231. var reserves = movements.Where(x =>
  232. x.JobRequisitionItem.ID != Guid.Empty && x.Job.ID == Item.RequisitionLink.JobLink.ID &&
  233. x.JobRequisitionItem.ID == Guid.Empty)
  234. .GroupBy(x => x.Location.ID);
  235. items.AddRange(
  236. CreateStockSelectionItems(
  237. reserves,
  238. (m) => $"{m.Job.JobNumber} (Unallocated Stock)"
  239. )
  240. );
  241. var freeitems = movements.Where(x => x.Job.ID == Guid.Empty && x.JobRequisitionItem.ID == Guid.Empty)
  242. .GroupBy(x => x.Location.ID);
  243. items.AddRange(
  244. CreateStockSelectionItems(
  245. freeitems,
  246. (m) => $"General Stock"
  247. )
  248. );
  249. Allocations.Items = items;
  250. Allocations.Refresh(true,true);
  251. }
  252. private void Allocations_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
  253. {
  254. UpdateMaximums();
  255. }
  256. private void UpdateMaximums()
  257. {
  258. foreach (var item in Allocations.Items)
  259. item.MaxValue = Item.Quantity + item.Issued - Allocations.Items.Sum(x=>x.Issued);
  260. }
  261. private void Allocations_OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
  262. {
  263. UpdateMaximums();
  264. CheckCanSave();
  265. }
  266. private bool CanSaveAllocations()
  267. {
  268. return Allocations.Items.Sum(x=>x.Issued).IsEffectivelyGreaterThan(0.0);
  269. }
  270. private RequisitionItem CopyRequisitionItem()
  271. {
  272. var newItem = new RequisitionItem();
  273. newItem.Done = Item.Done;
  274. newItem.Image.CopyFrom(Item.Image);
  275. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  276. newItem.SourceJRI.ID = Item.SourceJRI.ID;
  277. newItem.Product.CopyFrom(Item.Product);
  278. newItem.Style.CopyFrom(Item.Style);
  279. newItem.Location.CopyFrom(Item.Location);
  280. newItem.JobRequisitionItem.ID = Item.JobRequisitionItem.ID;
  281. newItem.Dimensions.CopyFrom(Item.Dimensions, true);
  282. newItem.JobLink.CopyFrom(Item.JobLink);
  283. newItem.Description = Item.Description;
  284. newItem.Quantity = Item.Quantity;
  285. newItem.EditType = RequisitionItemEditType.Normal;
  286. return newItem;
  287. }
  288. private void SaveAllocations()
  289. {
  290. var remainder = Item.Quantity;
  291. var newItems = new List<RequisitionItem>();
  292. foreach(var item in Allocations.Items.Where(x=>!x.Issued.IsEffectivelyEqual(0.0)))
  293. {
  294. var newItem = newItems.Count == 0 ? Item : CopyRequisitionItem();
  295. newItem.Location.CopyFrom(item.Holding.Location);
  296. newItem.JobRequisitionItem.CopyFrom(item.JRI);
  297. newItem.JobLink.CopyFrom(item.Holding.Job);
  298. // Automagically attach to Requisition if it seems appropriate
  299. // ie selected from a JRI allocation, and current source jri is blank
  300. if (newItem.SourceJRI.ID == Guid.Empty
  301. && newItem.RequisitionLink.JobLink.ID == item.Holding.Job.ID
  302. && item.JRI.ID != Guid.Empty)
  303. newItem.SourceJRI.ID = item.JRI.ID;
  304. newItem.Quantity = item.Issued;
  305. newItem.ActualQuantity = item.Issued;
  306. newItem.Done = true;
  307. remainder -= item.Issued;
  308. newItems.Add(newItem);
  309. }
  310. if(!remainder.IsEffectivelyEqual(0.0))
  311. {
  312. var newItem = CopyRequisitionItem();
  313. newItem.Location.CopyFrom(new StockLocationLink());
  314. newItem.JobRequisitionItem.CopyFrom(new JobRequisitionItemLink());
  315. newItem.JobLink.CopyFrom(new JobLink());
  316. newItem.Quantity = remainder;
  317. newItem.Done = false;
  318. newItems.Add(newItem);
  319. }
  320. Client.Save(newItems, "Updated from RequisitionItemEditor(Allocations)");
  321. Result = newItems.ToArray();
  322. }
  323. #endregion
  324. #region StockHoldings
  325. private void LoadStockHoldings()
  326. {
  327. StockHoldingProducts.Refresh(true, true);
  328. foreach(var column in StockHolding.Columns)
  329. {
  330. StockHoldings.HiddenColumns.Add(column);
  331. }
  332. foreach(var column in Columns.None<StockHolding>()
  333. .AddSubColumns(x => x.Product, LookupFactory.DefineLookupColumns<RequisitionItem, Product, ProductLink>(x => x.Product).CastIntersection<ProductLink>())
  334. .AddSubColumns(x => x.Style, LookupFactory.DefineLookupColumns<RequisitionItem, ProductStyle, ProductStyleLink>(x => x.Style).CastIntersection<ProductStyleLink>())
  335. .AddSubColumns(x => x.Location, LookupFactory.DefineLookupColumns<RequisitionItem, StockLocation, StockLocationLink>(x => x.Location).CastIntersection<StockLocationLink>())
  336. .AddSubColumns(x => x.Job, LookupFactory.DefineLookupColumns<RequisitionItem, Job, JobLink>(x => x.JobLink).CastIntersection<JobLink>())
  337. )
  338. {
  339. StockHoldings.HiddenColumns.Add(column);
  340. }
  341. StockHoldings.HiddenColumns.Add(x => x.Available);
  342. StockHoldings.Refresh(true, false);
  343. if(Item.Product.ID != Guid.Empty)
  344. {
  345. // Delaying, cause otherwise it doesn't work.
  346. Dispatcher.BeginInvoke(() =>
  347. {
  348. StockHoldingProducts.SelectedRows = StockHoldingProducts.Data.Rows.Where(x => x.Get<Product, Guid>(x => x.ID) == Item.Product.ID).ToArray();
  349. StockHoldingProducts.ScrollIntoView(StockHoldingProducts.SelectedRows[0]);
  350. });
  351. }
  352. }
  353. private void StockHoldingProducts_OnSelectItem(object sender, InABox.DynamicGrid.DynamicGridSelectionEventArgs e)
  354. {
  355. StockHoldings.Product = e.Rows is not null && e.Rows.Length > 0 ? e.Rows[0].ToObject<Product>() : null;
  356. StockHoldings.Refresh(false, true);
  357. CheckCanSave();
  358. }
  359. private void StockHoldings_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
  360. {
  361. var row = StockHoldings.Data.Rows.FirstOrDefault();
  362. if (row != null)
  363. {
  364. StockHoldings.SelectedRows = new CoreRow[] { row };
  365. SelectHolding(new CoreRow[] { row });
  366. }
  367. }
  368. private void StockHoldings_OnSelectItem(object sender, InABox.DynamicGrid.DynamicGridSelectionEventArgs e)
  369. {
  370. SelectHolding(e.Rows);
  371. }
  372. private void SelectHolding(CoreRow[]? rows)
  373. {
  374. StockHolding = rows?.FirstOrDefault()?.ToObject<StockHolding>();
  375. RefreshJobRequisitionItems();
  376. JRIRow.Height = _allowActual && StockHolding is not null ? new GridLength(1, GridUnitType.Star) : new GridLength(0);
  377. TotalRow.Height = _allowActual && StockHolding is not null ? new GridLength(40) : new GridLength(0);
  378. RequiredEditor.SetValue(Grid.ColumnProperty, _allowActual && StockHolding is not null ? 4 : 6);
  379. CheckCanSave();
  380. }
  381. private bool CanSaveStockHoldings()
  382. {
  383. return StockHolding is not null && TotalTaken > 0;
  384. }
  385. private void SaveStockHoldings()
  386. {
  387. if (StockHolding is null) return;
  388. var items = JRIItems.Where(x => x.Taken > 0).ToArray();
  389. if (items.Length == 0) return;
  390. Item.Product.CopyFrom(StockHolding.Product);
  391. Item.Style.CopyFrom(StockHolding.Style);
  392. Item.Location.CopyFrom(StockHolding.Location);
  393. Item.Dimensions.CopyFrom(StockHolding.Dimensions, true);
  394. Item.JobLink.CopyFrom(StockHolding.Job);
  395. Item.EditType = RequisitionItemEditType.Normal;
  396. var remainder = Item.Quantity;
  397. var newItems = new List<RequisitionItem>();
  398. foreach(var item in items)
  399. {
  400. RequisitionItem newItem;
  401. if(newItems.Count == 0)
  402. {
  403. newItem = Item;
  404. }
  405. else
  406. {
  407. newItem = new RequisitionItem();
  408. newItem.Done = Item.Done;
  409. newItem.Description = Item.Description;
  410. newItem.Image.CopyFrom(Item.Image);
  411. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  412. newItem.JobRequisitionItem.ID = item.ID;
  413. newItem.Product.CopyFrom(Item.Product);
  414. newItem.Style.CopyFrom(Item.Style);
  415. newItem.Location.CopyFrom(Item.Location);
  416. newItem.Dimensions.CopyFrom(Item.Dimensions, true);
  417. newItem.JobLink.CopyFrom(Item.JobLink);
  418. newItem.EditType = RequisitionItemEditType.Normal;
  419. }
  420. newItem.JobRequisitionItem.ID = item.ID;
  421. newItem.Quantity = item.Taken;
  422. newItem.ActualQuantity = item.Taken;
  423. remainder -= item.Taken;
  424. newItems.Add(newItem);
  425. }
  426. if(remainder > 0)
  427. {
  428. var newItem = new RequisitionItem();
  429. newItem.Done = false;
  430. newItem.Description = Item.Description;
  431. newItem.Image.CopyFrom(Item.Image);
  432. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  433. newItem.JobRequisitionItem.ID = Guid.Empty;
  434. newItem.Product.CopyFrom(Item.Product);
  435. newItem.Style.CopyFrom(Item.Style);
  436. newItem.Dimensions.CopyFrom(Item.Dimensions, true);
  437. newItem.Quantity = remainder;
  438. newItems.Add(newItem);
  439. }
  440. Client.Save(newItems, "Edited by user.");
  441. Result = newItems.ToArray();
  442. }
  443. #region JobRequisitionItem Selection
  444. public event PropertyChangedEventHandler? PropertyChanged;
  445. protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
  446. {
  447. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  448. }
  449. private bool _observing = false;
  450. public void SetObserving(bool observing)
  451. {
  452. if(_observing != observing)
  453. {
  454. _observing = observing;
  455. if (_observing)
  456. {
  457. Recalculate();
  458. }
  459. }
  460. }
  461. private StockHolding? StockHolding;
  462. private List<RequisitionItemStockHoldingItem> _jriItems;
  463. public List<RequisitionItemStockHoldingItem> JRIItems
  464. {
  465. get => _jriItems;
  466. set
  467. {
  468. _jriItems = value;
  469. OnPropertyChanged();
  470. }
  471. }
  472. private void RefreshJobRequisitionItems()
  473. {
  474. SetObserving(false);
  475. if(StockHolding is null)
  476. {
  477. JRIItems = [];
  478. }
  479. else
  480. {
  481. var jris = StockHolding.LoadRequisitionItems(true);
  482. JRIItems = jris.Select(x =>
  483. {
  484. var newItem = new RequisitionItemStockHoldingItem
  485. {
  486. Quantity = x.Qty,
  487. Taken = 0,
  488. ID = x.ID,
  489. Text = x.ID == Guid.Empty
  490. ? "General Stock"
  491. : $"{x.Job.JobNumber}:{x.Requisition.Number} {x.Requisition.Description} ({x.Qty})",
  492. };
  493. newItem.PropertyChanged += (o, e) => Recalculate();
  494. return newItem;
  495. }).ToList();
  496. }
  497. SetObserving(true);
  498. }
  499. private void Recalculate()
  500. {
  501. if (!_observing) return;
  502. TotalTaken = JRIItems.Sum(x => x.Taken);
  503. }
  504. private double _totalTaken;
  505. public double TotalTaken
  506. {
  507. get => _totalTaken;
  508. set
  509. {
  510. _totalTaken = value;
  511. OnPropertyChanged();
  512. CheckCanSave();
  513. }
  514. }
  515. public double RequiredQuantity
  516. {
  517. get => Item.Quantity;
  518. set
  519. {
  520. Item.Quantity = value;
  521. CheckCanSave();
  522. OnPropertyChanged();
  523. }
  524. }
  525. private void None_Click(object sender, RoutedEventArgs e)
  526. {
  527. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  528. item.Taken = 0;
  529. }
  530. private void Minus_Click(object sender, RoutedEventArgs e)
  531. {
  532. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  533. item.Taken = Math.Max(0, item.Taken - 1);
  534. }
  535. private void Plus_Click(object sender, RoutedEventArgs e)
  536. {
  537. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  538. item.Taken = Math.Min(item.Taken + 1, item.Quantity);
  539. }
  540. private void All_Click(object sender, RoutedEventArgs e)
  541. {
  542. if (sender is not FrameworkElement element || element.Tag is not RequisitionItemStockHoldingItem item) return;
  543. item.Taken = Math.Min(Math.Max(RequiredQuantity - (TotalTaken - item.Taken), 0), item.Quantity);
  544. }
  545. #endregion
  546. #endregion
  547. #region BOM Items
  548. private void LoadBOMItems()
  549. {
  550. BOMItems.Refresh(true,true);
  551. }
  552. private bool CanSaveBOMItems()
  553. {
  554. return BOMItems.Data.Rows.Any(r =>!r.Get<JobBOMSummary,double>(x=>x.Quantity).IsEffectivelyEqual(0.0));
  555. }
  556. private void SaveBOMItems()
  557. {
  558. var result = new List<RequisitionItem>() { Item };
  559. var bomItems = BOMItems.Data.Rows
  560. .Where(r => !r.Get<JobBOMSummary, double>(x => x.Quantity).IsEffectivelyEqual(0.0))
  561. .Select(r => r.ToObject<JobBOMSummary>()).ToArray();
  562. var first = bomItems.FirstOrDefault();
  563. if (first is null)
  564. return;
  565. Item.Product.CopyFrom(first.Product);
  566. Item.Style.CopyFrom(first.Style);
  567. Item.Dimensions.CopyFrom(first.Dimensions, true);
  568. Item.EditType = RequisitionItemEditType.Normal;
  569. Item.Quantity = first.Quantity;
  570. foreach (var item in bomItems.Skip(1).ToArray())
  571. {
  572. var newItem = new RequisitionItem();
  573. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  574. newItem.Done = false;
  575. newItem.Description = item.Product.Name;
  576. newItem.Product.CopyFrom(item.Product);
  577. newItem.Style.CopyFrom(item.Style);
  578. newItem.Dimensions.CopyFrom(item.Dimensions, true);
  579. newItem.EditType = RequisitionItemEditType.Normal;
  580. newItem.Quantity = item.Quantity;
  581. result.Add(newItem);
  582. }
  583. Client.Save(result, "Edited by user.");
  584. Result = result.ToArray();
  585. }
  586. private void BOMItems_OnOnChanged(object? sender, EventArgs e)
  587. {
  588. CheckCanSave();
  589. }
  590. #endregion
  591. #region Job Requisition Items
  592. private void LoadJobRequisitionItems()
  593. {
  594. JobRequisitionItemsGrid.Refresh(true,true);
  595. }
  596. private bool CanSaveJobRequisitionItems()
  597. {
  598. return JobRequisitionItemsGrid.Data.Rows.Any(r =>!r.Get<JobJRISummary,double>(x=>x.Quantity).IsEffectivelyEqual(0.0));
  599. }
  600. private void SaveJobRequisitionItems()
  601. {
  602. var result = new List<RequisitionItem>() { Item };
  603. var items = JobRequisitionItemsGrid.Data.Rows
  604. .Where(r => !r.Get<JobJRISummary, double>(x => x.Quantity).IsEffectivelyEqual(0.0))
  605. .Select(r => r.ToObject<JobJRISummary>()).ToArray();
  606. var first = items.FirstOrDefault();
  607. if (first is null)
  608. return;
  609. Item.Product.CopyFrom(first.JRI.Product);
  610. Item.Style.CopyFrom(first.JRI.Style);
  611. Item.Dimensions.CopyFrom(first.JRI.Dimensions, true);
  612. Item.JobRequisitionItem.CopyFrom(first.JRI);
  613. Item.EditType = RequisitionItemEditType.Normal;
  614. Item.Quantity = first.Quantity;
  615. foreach (var item in items.Skip(1).ToArray())
  616. {
  617. var newItem = new RequisitionItem();
  618. newItem.RequisitionLink.ID = Item.RequisitionLink.ID;
  619. newItem.Done = false;
  620. newItem.Description = item.JRI.Product.Name;
  621. newItem.Product.CopyFrom(item.JRI.Product);
  622. newItem.Style.CopyFrom(item.JRI.Style);
  623. newItem.Dimensions.CopyFrom(item.JRI.Dimensions, true);
  624. newItem.JobRequisitionItem.CopyFrom(item.JRI);
  625. newItem.EditType = RequisitionItemEditType.Normal;
  626. newItem.Quantity = item.Quantity;
  627. result.Add(newItem);
  628. }
  629. Client.Save(result, "Edited by user.");
  630. Result = result.ToArray();
  631. }
  632. private void JobRequisitionItems_OnOnChanged(object? sender, EventArgs e)
  633. {
  634. CheckCanSave();
  635. }
  636. #endregion
  637. #region Product Instances
  638. private ProductInstance? SelectedProductInstance;
  639. private void LoadProductInstances()
  640. {
  641. ProductInstancesProducts.Refresh(true, true);
  642. ProductInstances.Refresh(true, false);
  643. foreach(var column in Columns.None<ProductInstance>().Add(x => x.Style.ID).AddDimensionsColumns(x => x.Dimensions))
  644. {
  645. ProductInstances.HiddenColumns.Add(column);
  646. }
  647. if(Item.Product.ID != Guid.Empty)
  648. {
  649. // Delaying, cause otherwise it doesn't work.
  650. Dispatcher.BeginInvoke(() =>
  651. {
  652. ProductInstancesProducts.SelectedRows = ProductInstancesProducts.Data.Rows.Where(x => x.Get<Product, Guid>(x => x.ID) == Item.Product.ID).ToArray();
  653. if (ProductInstancesProducts.SelectedRows.Any())
  654. ProductInstancesProducts.ScrollIntoView(ProductInstancesProducts.SelectedRows[0]);
  655. });
  656. }
  657. }
  658. private bool CanSaveProductInstances()
  659. {
  660. return ProductInstances.Product is not null && SelectedProductInstance is not null && RequiredQuantity > 0;
  661. }
  662. private void SaveProductInstances()
  663. {
  664. if (ProductInstances.Product is null || SelectedProductInstance is null) return;
  665. Item.Product.CopyFrom(ProductInstances.Product);
  666. Item.Style.CopyFrom(SelectedProductInstance.Style);
  667. Item.Dimensions.CopyFrom(SelectedProductInstance.Dimensions, true);
  668. Item.Location.ID = Guid.Empty;
  669. Item.EditType = RequisitionItemEditType.Normal;
  670. Client.Save(Item, "Edited by user.");
  671. Result = [Item];
  672. }
  673. private void ProductInstancesProducts_OnSelectItem(object sender, InABox.DynamicGrid.DynamicGridSelectionEventArgs e)
  674. {
  675. ProductInstances.Product = e.Rows is not null && e.Rows.Length > 0 ? e.Rows[0].ToObject<Product>() : null;
  676. ProductInstances.Refresh(false, true);
  677. CheckCanSave();
  678. }
  679. private void ProductInstances_OnSelectItem(object sender, DynamicGridSelectionEventArgs e)
  680. {
  681. SelectedProductInstance = e.Rows is not null && e.Rows.Length > 0 ? e.Rows[0].ToObject<ProductInstance>() : null;
  682. CheckCanSave();
  683. }
  684. #endregion
  685. #region Custom
  686. private void LoadCustom()
  687. {
  688. CustomEditorGrid.Setup(typeof(RequisitionItem));
  689. CustomEditorGrid.Items = new RequisitionItem[] { Item };
  690. }
  691. private static readonly Columns<RequisitionItem> ActualCustomColumns = Columns.None<RequisitionItem>()
  692. .Add(x => x.Product.ID)
  693. .Add(x => x.Style.ID)
  694. .Add(x => x.Dimensions)
  695. .Add(x => x.Description)
  696. .Add(x => x.Location.ID)
  697. .Add(x => x.Quantity)
  698. .Add(x => x.ActualQuantity);
  699. private static readonly Columns<RequisitionItem> RequstedCustomColumns = Columns.None<RequisitionItem>()
  700. .Add(x => x.Product.ID)
  701. .Add(x => x.Style.ID)
  702. .Add(x => x.Dimensions)
  703. .Add(x => x.Description)
  704. .Add(x => x.Location.ID)
  705. .Add(x => x.Quantity)
  706. .Add(x => x.ActualQuantity);
  707. // private static readonly Columns<RequisitionItem> ProductInstanceCustomColumns = new Columns<RequisitionItem>()
  708. // .Add(x => x.Product.ID)
  709. // .Add(x => x.Style.ID)
  710. // .Add(x => x.Dimensions)
  711. // .Add(x => x.Description)
  712. // .Add(x => x.Quantity);
  713. private void CustomEditorGrid_OnFormCustomiseEditor(IDynamicEditorForm sender, object items, DynamicGridColumn column, BaseEditor editor)
  714. {
  715. var columns = _allowActual ? ActualCustomColumns : RequstedCustomColumns;
  716. editor.Editable = columns.Contains(column.ColumnName)
  717. ? Editable.Enabled
  718. : Editable.Hidden;
  719. }
  720. /*
  721. private void LoadCustom()
  722. {
  723. var host = new DefaultDynamicEditorHost<RequisitionItem>
  724. {
  725. Items = [Item]
  726. };
  727. var editors = new Dictionary<string, IDynamicEditorControl>();
  728. BaseDynamicEditorControl? CreateControl(Expression<Func<RequisitionItem, object?>> exp, int row)
  729. {
  730. var property = DatabaseSchema.Property(exp)!;
  731. BaseEditor? editor;
  732. if (property.HasParentEditor())
  733. {
  734. property = property.GetParentWithEditor();
  735. }
  736. if (property is null) return null;
  737. editor = property.Editor;
  738. editor = editor.CloneEditor();
  739. var control = DynamicEditorControlFactory.CreateControl(editor, host)!;
  740. Grid.SetRow(control, row);
  741. Grid.SetColumn(control, 1);
  742. control.Margin = new Thickness(5, 2.5, 5, 2.5);
  743. CustomEditorGrid.Children.Add(control);
  744. editors[property.Name] = control;
  745. control.IsEnabled = true;
  746. control.ColumnName = property.Name;
  747. control.Color = editor is UniqueCodeEditor ? Color.FromArgb(0xFF, 0xF6, 0xC9, 0xE8) : Colors.LightYellow;
  748. control.Configure();
  749. control.Loaded = true;
  750. foreach(var (k, v) in control.GetValues())
  751. {
  752. var value = DatabaseSchema.Property(typeof(RequisitionItem), k)!.Getter()(Item);
  753. if(!Equals(v, value))
  754. {
  755. bool bLoaded = control.Loaded;
  756. control.Loaded = false;
  757. control.SetValue(k, value);
  758. control.Loaded = bLoaded;
  759. }
  760. }
  761. control.OnEditorValueChanged += (sender, values) =>
  762. {
  763. var changededitors = new Dictionary<string, object?>();
  764. void ExtractChanged(Dictionary<string, object?>? columns)
  765. {
  766. if (columns != null)
  767. foreach (var (change, value) in columns)
  768. if (!changededitors.ContainsKey(change) && !change.Equals(sender.ColumnName))
  769. changededitors[change] = value;
  770. }
  771. var name = sender.ColumnName;
  772. var resetAll = false;
  773. if(values.Remove(name, out var value))
  774. {
  775. var changedcolumns = DynamicGridUtils.UpdateEditorValue([Item], sender.ColumnName, value);
  776. resetAll = changedcolumns?.ContainsKey(name) ?? false;
  777. ExtractChanged(changedcolumns);
  778. }
  779. foreach(var (k, v) in values)
  780. {
  781. var changes = new Dictionary<string, object?>();
  782. if (resetAll)
  783. {
  784. var prop = DatabaseSchema.Property(typeof(RequisitionItem), k);
  785. if(prop is not null)
  786. {
  787. var def = CoreUtils.GetDefault(prop.PropertyType);
  788. var resetChanges = DynamicGridUtils.UpdateEditorValue([Item], k, def);
  789. if(resetChanges is not null)
  790. {
  791. changes = resetChanges;
  792. }
  793. }
  794. else
  795. {
  796. }
  797. }
  798. var changedOtherColumns = DynamicGridUtils.UpdateEditorValue([Item], k, v);
  799. if(changedOtherColumns is not null)
  800. {
  801. foreach (var (k1, v1) in changedOtherColumns)
  802. {
  803. changes[k1] = v1;
  804. }
  805. }
  806. ExtractChanged(changes);
  807. }
  808. if (changededitors.Count != 0)
  809. {
  810. foreach(var (k, v) in changededitors)
  811. {
  812. var editor = editors.GetValueOrDefault(k) ?? editors.FirstOrDefault(x => k.StartsWith(x.Key + ".")).Value;
  813. if(editor is not null)
  814. {
  815. editor.Loaded = false;
  816. editor.SetValue(k, v);
  817. editor.Loaded = true;
  818. }
  819. }
  820. }
  821. };
  822. return control;
  823. }
  824. var productEditor = CreateControl(x => x.Product.ID, 0);
  825. var styleEditor = CreateControl(x => x.Style.ID, 1);
  826. var dimensionsEditor = CreateControl(x => x.Dimensions.Quantity, 2);
  827. var descriptionEditor = CreateControl(x => x.Description, 3);
  828. var locationEditor = CreateControl(x => x.Location.ID, 4);
  829. var quantityEditor = CreateControl(x => x.Quantity, 5);
  830. }
  831. */
  832. private void SaveCustom()
  833. {
  834. Item.EditType = RequisitionItemEditType.Custom;
  835. Client.Save(Item, "Custom edited by user.");
  836. Result = [Item];
  837. }
  838. #endregion
  839. }