StockSummaryOrderingGrid.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using InABox.Wpf;
  6. using InABox.WPF;
  7. using NPOI.SS.Formula.Functions;
  8. using Syncfusion.Data;
  9. using Syncfusion.Data.Extensions;
  10. using Syncfusion.UI.Xaml.Grid;
  11. using Syncfusion.Windows.Shared;
  12. using System;
  13. using System.Collections;
  14. using System.Collections.Generic;
  15. using System.ComponentModel;
  16. using System.Data;
  17. using System.Linq;
  18. using System.Text;
  19. using System.Threading.Tasks;
  20. using System.Windows;
  21. using System.Windows.Controls;
  22. using System.Windows.Media;
  23. using System.Windows.Media.Imaging;
  24. namespace PRSDesktop.Panels.StockSummary.OrderScreen;
  25. public enum StockSummaryOrderingType
  26. {
  27. StockOrder,
  28. JobOrder
  29. }
  30. public class StockSummaryOrderingItemQuantity
  31. {
  32. public event Action? Changed;
  33. private double _stockTotal;
  34. public double StockTotal
  35. {
  36. get => _stockTotal;
  37. set
  38. {
  39. _stockTotal = value;
  40. Changed?.Invoke();
  41. }
  42. }
  43. public Dictionary<Guid, double> JobTotals { get; init; } = [];
  44. public void DoChanged()
  45. {
  46. Changed?.Invoke();
  47. }
  48. public double JobTotal => JobTotals.Sum(x => x.Value);
  49. public double GetTotal(StockSummaryOrderingType type) => type == StockSummaryOrderingType.StockOrder
  50. ? StockTotal
  51. : JobTotal;
  52. }
  53. public class StockSummaryOrderingItem : BaseObject
  54. {
  55. [EditorSequence(1)]
  56. public ProductLink Product { get; set; }
  57. [EditorSequence(2)]
  58. public ProductStyleLink Style { get; set; }
  59. [EditorSequence(3)]
  60. public StockDimensions Dimensions { get; set; }
  61. [EditorSequence(4)]
  62. [DoubleEditor]
  63. public double RequiredQuantity { get; set; }
  64. private Dictionary<Guid, double> JobRequiredQuantities { get; set; } = [];
  65. public Dictionary<Guid, double> GetJobRequiredQuantities()
  66. {
  67. return JobRequiredQuantities;
  68. }
  69. public void SetJobRequiredQuantity(Guid jobID, double requiredQty)
  70. {
  71. JobRequiredQuantities[jobID] = requiredQty;
  72. }
  73. private StockSummaryOrderingItemQuantity[] Quantities = [];
  74. public StockSummaryOrderingItemQuantity GetQuantity(int i) => Quantities[i];
  75. public double GetTotalQuantity(StockSummaryOrderingType type) => type == StockSummaryOrderingType.StockOrder
  76. ? Quantities.Sum(x => x.StockTotal)
  77. : Quantities.Sum(x => x.JobTotal);
  78. public void SetQuantities(StockSummaryOrderingItemQuantity[] quantities)
  79. {
  80. Quantities = quantities;
  81. }
  82. }
  83. public class StockSummaryOrderingResult
  84. {
  85. public SupplierLink Supplier { get; set; }
  86. public JobLink? Job { get; set; }
  87. public StockSummaryOrderingItem Item { get; set; }
  88. public SupplierProduct SupplierProduct { get; set; }
  89. public double Quantity { get; set; }
  90. public StockSummaryOrderingResult(SupplierLink supplier, JobLink? job, StockSummaryOrderingItem item, double quantity, SupplierProduct supplierProduct)
  91. {
  92. Supplier = supplier;
  93. Job = job;
  94. Item = item;
  95. Quantity = quantity;
  96. SupplierProduct = supplierProduct;
  97. }
  98. }
  99. public class StockSummaryOrderingGrid : DynamicItemsListGrid<StockSummaryOrderingItem>, ISpecificGrid
  100. {
  101. private SupplierProduct[] SupplierProducts = [];
  102. private SupplierLink[] Suppliers = [];
  103. public double TotalQuantity => Items.Sum(x => x.GetTotalQuantity(OrderType));
  104. private DynamicActionColumn[] QuantityColumns = [];
  105. private DynamicActionColumn[] CostColumns = [];
  106. private readonly Dictionary<Guid, Job> JobDetails = [];
  107. private static BitmapImage _warning = PRSDesktop.Resources.warning.AsBitmapImage();
  108. private StockSummaryOrderingType _orderType = StockSummaryOrderingType.JobOrder;
  109. public StockSummaryOrderingType OrderType
  110. {
  111. get => _orderType;
  112. set
  113. {
  114. if(_orderType != value)
  115. {
  116. _orderType = value;
  117. CalculateQuantities();
  118. foreach(var control in QuantityControls)
  119. {
  120. control.UpdateControl(OrderType);
  121. }
  122. }
  123. }
  124. }
  125. public IEnumerable<StockSummaryOrderingResult> Results
  126. {
  127. get
  128. {
  129. for(int i = 0; i < Suppliers.Length; ++i)
  130. {
  131. var supplier = Suppliers[i];
  132. foreach(var item in Items)
  133. {
  134. var qty = item.GetQuantity(i);
  135. var supplierProduct = GetSupplierProduct(item, supplier.ID);
  136. if (supplierProduct is null)
  137. {
  138. // If this is true, then the quantities also will have to be true.
  139. continue;
  140. }
  141. if(OrderType == StockSummaryOrderingType.StockOrder && qty.StockTotal > 0)
  142. {
  143. yield return new(supplier, null, item, qty.StockTotal, supplierProduct);
  144. }
  145. else
  146. {
  147. foreach(var (jobID, q) in qty.JobTotals)
  148. {
  149. if(q > 0)
  150. {
  151. yield return new(supplier, new() { ID = jobID }, item, q, supplierProduct);
  152. }
  153. }
  154. }
  155. }
  156. }
  157. }
  158. }
  159. #region UI Component
  160. private Component? _uiComponent;
  161. private Component UIComponent
  162. {
  163. get
  164. {
  165. _uiComponent ??= new Component(this);
  166. return _uiComponent;
  167. }
  168. }
  169. protected override IDynamicGridUIComponent<StockSummaryOrderingItem> CreateUIComponent()
  170. {
  171. return UIComponent;
  172. }
  173. private class Component : DynamicGridGridUIComponent<StockSummaryOrderingItem>
  174. {
  175. private StockSummaryOrderingGrid Grid;
  176. public Component(StockSummaryOrderingGrid grid)
  177. {
  178. Parent = grid;
  179. Grid = grid;
  180. }
  181. protected override Brush? GetCellSelectionBackgroundBrush()
  182. {
  183. return null;
  184. }
  185. protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
  186. {
  187. var item = Grid.LoadItem(row);
  188. if(column is DynamicActionColumn ac)
  189. {
  190. var qIdx = Grid.QuantityColumns.IndexOf(ac);
  191. var idx = Math.Max(qIdx, Grid.CostColumns.IndexOf(ac));
  192. if(idx != -1)
  193. {
  194. var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[idx].ID);
  195. if(supplierProduct is null)
  196. {
  197. return new SolidColorBrush(Colors.Gainsboro);
  198. }
  199. //if(item.GetTotalQuantity(Grid.OrderType) < item.RequiredQuantity)
  200. //{
  201. // return new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 };
  202. //}
  203. //else
  204. //{
  205. // return new SolidColorBrush(Colors.LightGreen) { Opacity = 0.5 };
  206. //}
  207. }
  208. }
  209. return base.GetCellBackground(row, column);
  210. }
  211. }
  212. #endregion
  213. private bool _observing = true;
  214. private void SetObserving(bool observing)
  215. {
  216. _observing = observing;
  217. }
  218. public override void DoChanged()
  219. {
  220. if (_observing)
  221. {
  222. base.DoChanged();
  223. }
  224. }
  225. protected override void DoReconfigure(FluentList<DynamicGridOption> options)
  226. {
  227. options.Clear().Add(DynamicGridOption.FilterRows);
  228. }
  229. private void LoadData()
  230. {
  231. var supplierColumns = new Columns<SupplierProduct>(x => x.ID)
  232. .Add(x => x.SupplierLink.ID)
  233. .Add(x => x.Product.ID)
  234. .Add(x => x.Style.ID)
  235. .Add(x => x.CostPrice)
  236. .AddDimensionsColumns(x => x.Dimensions)
  237. .Add(x => x.SupplierLink.Code);
  238. SupplierProducts = Client.Query(
  239. new Filter<SupplierProduct>(x => x.Product.ID).InList(Items.Select(x => x.Product.ID).ToArray())
  240. .And(x => x.SupplierLink.ID).IsNotEqualTo(Guid.Empty),
  241. supplierColumns,
  242. new SortOrder<SupplierProduct>(x => x.SupplierLink.Code))
  243. .ToArray<SupplierProduct>();
  244. Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).ToArray();
  245. foreach(var (itemIdx, item) in Items.WithIndex())
  246. {
  247. var quantities = new StockSummaryOrderingItemQuantity[Suppliers.Length];
  248. for(int i = 0; i < Suppliers.Length; ++i)
  249. {
  250. var qty = new StockSummaryOrderingItemQuantity();
  251. quantities[i] = qty;
  252. qty.Changed += () =>
  253. {
  254. var row = Data.Rows[itemIdx];
  255. InvalidateRow(row);
  256. DoChanged();
  257. };
  258. }
  259. item.SetQuantities(quantities);
  260. }
  261. CalculateQuantities();
  262. }
  263. private void CalculateQuantities()
  264. {
  265. SetObserving(false);
  266. foreach(var item in Items)
  267. {
  268. var supplierProduct = GetSupplierProduct(item);
  269. for(int i = 0; i < Suppliers.Length; ++i)
  270. {
  271. var qty = item.GetQuantity(i);
  272. var supplier = Suppliers[i];
  273. if(supplierProduct is not null && supplier.ID == supplierProduct.SupplierLink.ID)
  274. {
  275. if(OrderType == StockSummaryOrderingType.StockOrder)
  276. {
  277. qty.StockTotal = qty.JobTotal;
  278. }
  279. else
  280. {
  281. qty.JobTotals.Clear();
  282. foreach(var (id, q) in item.GetJobRequiredQuantities())
  283. {
  284. qty.JobTotals[id] = q;
  285. }
  286. }
  287. }
  288. else
  289. {
  290. if(OrderType == StockSummaryOrderingType.StockOrder)
  291. {
  292. qty.StockTotal = 0;
  293. }
  294. else
  295. {
  296. foreach(var id in qty.JobTotals.Keys)
  297. {
  298. qty.JobTotals[id] = 0;
  299. }
  300. }
  301. }
  302. }
  303. }
  304. SetObserving(true);
  305. DoChanged();
  306. InvalidateGrid();
  307. }
  308. protected override DynamicGridColumns LoadColumns()
  309. {
  310. LoadData();
  311. ActionColumns.Add(new DynamicImageColumn(Warning_Image) { Position = DynamicActionColumnPosition.Start });
  312. var columns = new DynamicGridColumns();
  313. columns.Add<StockSummaryOrderingItem, string>(x => x.Product.Code, 120, "Product", "", Alignment.MiddleCenter);
  314. columns.Add<StockSummaryOrderingItem, string>(x => x.Style.Code, 120, "Style", "", Alignment.MiddleCenter);
  315. columns.Add<StockSummaryOrderingItem, string>(x => x.Dimensions.UnitSize, 0, "Size", "", Alignment.MiddleLeft);
  316. columns.Add<StockSummaryOrderingItem, double>(x => x.RequiredQuantity, 0, "Required", "", Alignment.MiddleLeft);
  317. QuantityColumns = new DynamicActionColumn[Suppliers.Length];
  318. CostColumns = new DynamicActionColumn[Suppliers.Length];
  319. QuantityControls.Clear();
  320. for(int i = 0; i < Suppliers.Length; ++i)
  321. {
  322. InitialiseSupplierColumn(i);
  323. }
  324. return columns;
  325. }
  326. private BitmapImage? Warning_Image(CoreRow? row)
  327. {
  328. if (row is null) return _warning;
  329. var item = LoadItem(row);
  330. if(item.GetTotalQuantity(OrderType) < item.RequiredQuantity)
  331. {
  332. return _warning;
  333. }
  334. else
  335. {
  336. return null;
  337. }
  338. }
  339. protected override void ConfigureColumnGroups()
  340. {
  341. for(int idx = 0; idx < Suppliers.Length; ++idx)
  342. {
  343. GetColumnGrouping().AddGroup(Suppliers[idx].Code, QuantityColumns[idx], CostColumns[idx]);
  344. }
  345. }
  346. private void LoadJobData(IEnumerable<Guid> ids)
  347. {
  348. var neededIDs = ids.Where(x => !JobDetails.ContainsKey(x)).ToArray();
  349. if(neededIDs.Length > 0)
  350. {
  351. var details = Client.Query(
  352. new Filter<Job>(x => x.ID).InList(neededIDs),
  353. new Columns<Job>(x => x.ID)
  354. .Add(x => x.JobNumber)
  355. .Add(x => x.Name));
  356. foreach(var job in details.ToObjects<Job>())
  357. {
  358. JobDetails[job.ID] = job;
  359. }
  360. }
  361. }
  362. private class QuantityControl : ContentControl
  363. {
  364. private readonly StockSummaryOrderingItem Item;
  365. private readonly int SupplierIndex;
  366. private readonly StockSummaryOrderingGrid Parent;
  367. public QuantityControl(StockSummaryOrderingGrid parent, StockSummaryOrderingItem item, int supplierIndex, StockSummaryOrderingType mode)
  368. {
  369. Parent = parent;
  370. Item = item;
  371. SupplierIndex = supplierIndex;
  372. UpdateControl(mode);
  373. }
  374. public void UpdateControl(StockSummaryOrderingType mode)
  375. {
  376. var supplierProduct = Parent.GetSupplierProduct(Item, Parent.Suppliers[SupplierIndex].ID);
  377. if(supplierProduct is null)
  378. {
  379. Content = null;
  380. return;
  381. }
  382. if(mode == StockSummaryOrderingType.StockOrder)
  383. {
  384. var editor = new DoubleTextBox
  385. {
  386. VerticalAlignment = VerticalAlignment.Stretch,
  387. HorizontalAlignment = HorizontalAlignment.Stretch,
  388. Background = new SolidColorBrush(Colors.LightYellow),
  389. BorderThickness = new Thickness(0.0),
  390. MinValue = 0.0,
  391. Value = Item.GetQuantity(SupplierIndex).StockTotal
  392. };
  393. editor.ValueChanged += (o, e) =>
  394. {
  395. Item.GetQuantity(SupplierIndex).StockTotal = editor.Value ?? default;
  396. };
  397. Content = editor;
  398. }
  399. else if(mode == StockSummaryOrderingType.JobOrder)
  400. {
  401. var grid = new Grid();
  402. grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
  403. grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
  404. var editor = new TextBox
  405. {
  406. VerticalAlignment = VerticalAlignment.Stretch,
  407. HorizontalAlignment = HorizontalAlignment.Stretch,
  408. VerticalContentAlignment = VerticalAlignment.Center,
  409. HorizontalContentAlignment = HorizontalAlignment.Center,
  410. Background = new SolidColorBrush(Colors.White),
  411. BorderThickness = new Thickness(0.0),
  412. IsReadOnly = true,
  413. Text = string.Format("{0:F2}", Item.GetQuantity(SupplierIndex).JobTotal)
  414. };
  415. Grid.SetColumn(editor, 0);
  416. grid.Children.Add(editor);
  417. var btn = new Button
  418. {
  419. VerticalAlignment = VerticalAlignment.Stretch,
  420. VerticalContentAlignment = VerticalAlignment.Center,
  421. HorizontalAlignment = HorizontalAlignment.Stretch,
  422. Content = "..",
  423. Margin = new Thickness(1),
  424. Focusable = false
  425. };
  426. btn.SetValue(Grid.ColumnProperty, 1);
  427. btn.SetValue(Grid.RowProperty, 0);
  428. btn.Click += (o, e) =>
  429. {
  430. var qty = Item.GetQuantity(SupplierIndex);
  431. Parent.LoadJobData(qty.JobTotals.Keys);
  432. var items = qty.JobTotals.Select(x =>
  433. {
  434. var item = new StockSummaryOrderingJobItem
  435. {
  436. JobID = x.Key,
  437. RequiredQuantity = Item.GetJobRequiredQuantities().GetValueOrDefault(x.Key),
  438. Quantity = x.Value
  439. };
  440. if(item.JobID == Guid.Empty)
  441. {
  442. item.Job = "General Stock";
  443. }
  444. else if(Parent.JobDetails.TryGetValue(item.JobID, out var job))
  445. {
  446. item.Job = $"{job.JobNumber}: {job.Name}";
  447. }
  448. return item;
  449. }).ToList();
  450. var window = new StockSummaryOrderJobScreen();
  451. window.Items = items;
  452. if(window.ShowDialog() == true)
  453. {
  454. foreach(var item in items)
  455. {
  456. qty.JobTotals[item.JobID] = item.Quantity;
  457. }
  458. qty.DoChanged();
  459. editor.Text = string.Format("{0:F2}", Item.GetQuantity(SupplierIndex).JobTotal);
  460. }
  461. };
  462. grid.Children.Add(btn);
  463. Content = grid;
  464. }
  465. }
  466. }
  467. private List<QuantityControl> QuantityControls = [];
  468. private void InitialiseSupplierColumn(int idx)
  469. {
  470. var supplierProducts = SupplierProducts.Where(x => x.SupplierLink.ID == Suppliers[idx].ID).ToArray();
  471. // Making local copy of index so that the lambda can use it, and not the changed value of 'i'.
  472. var qtyColumn = new Tuple<DynamicActionColumn, QuantityControl?>(null!, null);
  473. QuantityColumns[idx] = new DynamicTemplateColumn(row =>
  474. {
  475. var instance = LoadItem(row);
  476. var control = new QuantityControl(this, instance, idx, OrderType);
  477. QuantityControls.Add(control);
  478. return control;
  479. })
  480. {
  481. HeaderText = "Qty.",
  482. Width = 80
  483. };
  484. CostColumns[idx] = new DynamicTextColumn(row =>
  485. {
  486. if(row is null)
  487. {
  488. return "Cost";
  489. }
  490. var instance = LoadItem(row);
  491. var qty = OrderType == StockSummaryOrderingType.StockOrder
  492. ? instance.GetQuantity(idx).StockTotal
  493. : instance.GetQuantity(idx).JobTotal;
  494. var supplierProduct = GetSupplierProduct(instance, Suppliers[idx].ID);
  495. if(supplierProduct is not null)
  496. {
  497. return $"{qty * supplierProduct.CostPrice:C2}";
  498. }
  499. else
  500. {
  501. return "";
  502. }
  503. })
  504. {
  505. HeaderText = "Cost",
  506. Width = 80,
  507. GetSummary = () =>
  508. {
  509. var i = idx * 2 + 1;
  510. var summary = new GridSummaryColumn
  511. {
  512. Format = "{Sum:C2}",
  513. SummaryType = Syncfusion.Data.SummaryType.Custom,
  514. CustomAggregate = new CostAggregate(idx, this)
  515. };
  516. return summary;
  517. }
  518. };
  519. ActionColumns.Add(QuantityColumns[idx]);
  520. ActionColumns.Add(CostColumns[idx]);
  521. }
  522. private static bool Matches(StockSummaryOrderingItem item, SupplierProduct supplierProduct)
  523. {
  524. return item.Product.ID == supplierProduct.Product.ID
  525. && item.Style.ID == supplierProduct.Style.ID
  526. && item.Dimensions.Equals(supplierProduct.Dimensions);
  527. }
  528. private static bool Matches(ProductInstance instance, SupplierProduct supplierProduct)
  529. {
  530. return instance.Product.ID == supplierProduct.Product.ID
  531. && instance.Style.ID == supplierProduct.Style.ID
  532. && instance.Dimensions.Equals(supplierProduct.Dimensions);
  533. }
  534. private SupplierProduct? GetSupplierProduct(StockSummaryOrderingItem item)
  535. {
  536. var defaultSupplierProduct = SupplierProducts.FirstOrDefault(x => x.ID == item.Product.Supplier.ID);
  537. if(defaultSupplierProduct is not null && Matches(item, defaultSupplierProduct))
  538. {
  539. return defaultSupplierProduct;
  540. }
  541. else
  542. {
  543. return SupplierProducts.FirstOrDefault(x => Matches(item, x));
  544. }
  545. }
  546. private SupplierProduct? GetSupplierProduct(ProductInstance instance, Guid supplierID)
  547. {
  548. return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(instance, x));
  549. }
  550. private SupplierProduct? GetSupplierProduct(StockSummaryOrderingItem item, Guid supplierID)
  551. {
  552. return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(item, x));
  553. }
  554. //private double GetQuantity(SupplierProduct product)
  555. //{
  556. // var instance = ProductInstances.WithIndex().Where(x => x.Value.Product.ID == product.ID)
  557. //}
  558. private class CostAggregate : ISummaryAggregate
  559. {
  560. public double Sum { get; private set; }
  561. private int SupplierIndex;
  562. private StockSummaryOrderingGrid Grid;
  563. public CostAggregate(int supplierIndex, StockSummaryOrderingGrid grid)
  564. {
  565. SupplierIndex = supplierIndex;
  566. Grid = grid;
  567. }
  568. public Action<IEnumerable, string, PropertyDescriptor> CalculateAggregateFunc()
  569. {
  570. return AggregateFunc;
  571. }
  572. private void AggregateFunc(IEnumerable items, string property, PropertyDescriptor args)
  573. {
  574. if (items is IEnumerable<DataRowView> rows)
  575. {
  576. Sum = 0;
  577. foreach (var dataRow in rows)
  578. {
  579. var rowIdx = dataRow.Row.Table.Rows.IndexOf(dataRow.Row);
  580. var item = Grid.LoadItem(Grid.Data.Rows[rowIdx]);
  581. var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[SupplierIndex].ID);
  582. if(supplierProduct is not null)
  583. {
  584. var qty = item.GetQuantity(SupplierIndex);
  585. Sum += qty.GetTotal(Grid.OrderType) * supplierProduct.CostPrice;
  586. }
  587. }
  588. }
  589. else
  590. {
  591. Logger.Send(LogType.Error, "", $"Attempting to calculate aggregate on invalid data type '{items.GetType()}'.");
  592. }
  593. }
  594. }
  595. }