StockForecastOrderingGrid.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823
  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.StockForecast.OrderScreen;
  25. public enum StockForecastOrderingType
  26. {
  27. StockOrder,
  28. JobOrder
  29. }
  30. public class StockForecastOrderingItemQuantity
  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(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
  50. ? StockTotal
  51. : JobTotal;
  52. }
  53. public class StockForecastOrderingItem : 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 StockForecastOrderingItemQuantity[] Quantities = [];
  74. public StockForecastOrderingItemQuantity GetQuantity(int i) => Quantities[i];
  75. public double GetTotalQuantity(StockForecastOrderingType type) => type == StockForecastOrderingType.StockOrder
  76. ? Quantities.Sum(x => x.StockTotal)
  77. : Quantities.Sum(x => x.JobTotal);
  78. public void SetQuantities(StockForecastOrderingItemQuantity[] quantities)
  79. {
  80. Quantities = quantities;
  81. }
  82. }
  83. public class StockForecastOrderingResult
  84. {
  85. public SupplierLink Supplier { get; set; }
  86. public JobLink? Job { get; set; }
  87. public StockForecastOrderingItem Item { get; set; }
  88. public SupplierProduct SupplierProduct { get; set; }
  89. public double Quantity { get; set; }
  90. public StockForecastOrderingResult(SupplierLink supplier, JobLink? job, StockForecastOrderingItem 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 StockForecastOrderingGrid : DynamicItemsListGrid<StockForecastOrderingItem>, ISpecificGrid
  100. {
  101. private List<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 StockForecastOrderingType _orderType = StockForecastOrderingType.JobOrder;
  109. public StockForecastOrderingType 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<StockForecastOrderingResult> 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 == StockForecastOrderingType.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. public StockForecastOrderingGrid()
  160. {
  161. HiddenColumns.Add(x => x.Product.Image.ID);
  162. }
  163. #region UI Component
  164. private Component? _uiComponent;
  165. private Component UIComponent
  166. {
  167. get
  168. {
  169. _uiComponent ??= new Component(this);
  170. return _uiComponent;
  171. }
  172. }
  173. protected override IDynamicGridUIComponent<StockForecastOrderingItem> CreateUIComponent()
  174. {
  175. return UIComponent;
  176. }
  177. private class Component : DynamicGridGridUIComponent<StockForecastOrderingItem>
  178. {
  179. private StockForecastOrderingGrid Grid;
  180. public Component(StockForecastOrderingGrid grid)
  181. {
  182. Parent = grid;
  183. Grid = grid;
  184. DataGrid.FrozenColumnCount = 7;
  185. }
  186. protected override Brush? GetCellSelectionBackgroundBrush()
  187. {
  188. return null;
  189. }
  190. protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
  191. {
  192. var item = Grid.LoadItem(row);
  193. if(column is DynamicActionColumn ac)
  194. {
  195. var qIdx = Grid.QuantityColumns.IndexOf(ac);
  196. var idx = Math.Max(qIdx, Grid.CostColumns.IndexOf(ac));
  197. if(idx != -1)
  198. {
  199. var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[idx].ID);
  200. if(supplierProduct is null)
  201. {
  202. return new SolidColorBrush(Colors.Gainsboro);
  203. }
  204. //if(item.GetTotalQuantity(Grid.OrderType) < item.RequiredQuantity)
  205. //{
  206. // return new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 };
  207. //}
  208. //else
  209. //{
  210. // return new SolidColorBrush(Colors.LightGreen) { Opacity = 0.5 };
  211. //}
  212. }
  213. }
  214. return base.GetCellBackground(row, column);
  215. }
  216. }
  217. #endregion
  218. private bool _observing = true;
  219. private void SetObserving(bool observing)
  220. {
  221. _observing = observing;
  222. }
  223. public override void DoChanged()
  224. {
  225. if (_observing)
  226. {
  227. base.DoChanged();
  228. }
  229. }
  230. protected override void DoReconfigure(FluentList<DynamicGridOption> options)
  231. {
  232. options.Clear().Add(DynamicGridOption.FilterRows);
  233. }
  234. private bool _loadedData = false;
  235. private void LoadData()
  236. {
  237. var supplierColumns = new Columns<SupplierProduct>(x => x.ID)
  238. .Add(x => x.SupplierLink.ID)
  239. .Add(x => x.Product.ID)
  240. .Add(x => x.Style.ID)
  241. .Add(x => x.CostPrice)
  242. .AddDimensionsColumns(x => x.Dimensions)
  243. .Add(x => x.SupplierLink.Code);
  244. SupplierProducts = Client.Query(
  245. new Filter<SupplierProduct>(x => x.Product.ID).InList(Items.Select(x => x.Product.ID).ToArray())
  246. .And(x => x.SupplierLink.ID).IsNotEqualTo(Guid.Empty),
  247. supplierColumns,
  248. new SortOrder<SupplierProduct>(x => x.SupplierLink.Code))
  249. .ToList<SupplierProduct>();
  250. Suppliers = SupplierProducts.Select(x => x.SupplierLink).DistinctBy(x => x.ID).ToArray();
  251. foreach(var (itemIdx, item) in Items.WithIndex())
  252. {
  253. var quantities = new StockForecastOrderingItemQuantity[Suppliers.Length];
  254. for(int i = 0; i < Suppliers.Length; ++i)
  255. {
  256. quantities[i] = CreateQuantity(itemIdx);
  257. }
  258. item.SetQuantities(quantities);
  259. }
  260. CalculateQuantities();
  261. _loadedData = true;
  262. }
  263. private StockForecastOrderingItemQuantity CreateQuantity(int itemIdx)
  264. {
  265. var qty = new StockForecastOrderingItemQuantity();
  266. qty.Changed += () =>
  267. {
  268. var row = Data.Rows[itemIdx];
  269. InvalidateRow(row);
  270. DoChanged();
  271. };
  272. return qty;
  273. }
  274. private void CalculateQuantities()
  275. {
  276. SetObserving(false);
  277. foreach(var item in Items)
  278. {
  279. var supplierProduct = GetSupplierProduct(item);
  280. for(int i = 0; i < Suppliers.Length; ++i)
  281. {
  282. var qty = item.GetQuantity(i);
  283. var supplier = Suppliers[i];
  284. if(supplierProduct is not null && supplier.ID == supplierProduct.SupplierLink.ID)
  285. {
  286. if(OrderType == StockForecastOrderingType.StockOrder)
  287. {
  288. qty.StockTotal = qty.JobTotal;
  289. }
  290. else
  291. {
  292. qty.JobTotals.Clear();
  293. foreach(var (id, q) in item.GetJobRequiredQuantities())
  294. {
  295. qty.JobTotals[id] = q;
  296. }
  297. }
  298. }
  299. else
  300. {
  301. if(OrderType == StockForecastOrderingType.StockOrder)
  302. {
  303. qty.StockTotal = 0;
  304. }
  305. else
  306. {
  307. foreach(var id in item.GetJobRequiredQuantities().Keys)
  308. {
  309. qty.JobTotals[id] = 0;
  310. }
  311. }
  312. }
  313. }
  314. }
  315. SetObserving(true);
  316. DoChanged();
  317. InvalidateGrid();
  318. }
  319. protected override DynamicGridColumns LoadColumns()
  320. {
  321. if (!_loadedData)
  322. {
  323. LoadData();
  324. }
  325. ActionColumns.Clear();
  326. ActionColumns.Add(new DynamicImageColumn(Warning_Image) { Position = DynamicActionColumnPosition.Start });
  327. ActionColumns.Add(new DynamicImagePreviewColumn<StockForecastOrderingItem>(x => x.Product.Image) { Position = DynamicActionColumnPosition.Start });
  328. var columns = new DynamicGridColumns();
  329. columns.Add<StockForecastOrderingItem, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
  330. columns.Add<StockForecastOrderingItem, string>(x => x.Product.Name, 200, "Product Name", "", Alignment.MiddleLeft);
  331. columns.Add<StockForecastOrderingItem, string>(x => x.Style.Code, 80, "Style", "", Alignment.MiddleCenter);
  332. columns.Add<StockForecastOrderingItem, string>(x => x.Dimensions.UnitSize, 80, "Size", "", Alignment.MiddleCenter);
  333. columns.Add<StockForecastOrderingItem, double>(x => x.RequiredQuantity, 80, "Required", "", Alignment.MiddleCenter);
  334. QuantityColumns = new DynamicActionColumn[Suppliers.Length];
  335. CostColumns = new DynamicActionColumn[Suppliers.Length];
  336. QuantityControls.Clear();
  337. for(int i = 0; i < Suppliers.Length; ++i)
  338. {
  339. InitialiseSupplierColumn(i);
  340. }
  341. ActionColumns.Add(new DynamicMenuColumn(BuildMenu));
  342. return columns;
  343. }
  344. private void EditSupplierProductGrid(DynamicGrid<SupplierProduct> grid)
  345. {
  346. grid.OnCustomiseEditor += (sender, items, column, editor) =>
  347. {
  348. if(new Column<SupplierProduct>(x => x.SupplierLink.ID).IsEqualTo(column.ColumnName)
  349. || new Column<SupplierProduct>(x => x.Product.ID).IsEqualTo(column.ColumnName)
  350. || new Column<SupplierProduct>(x => x.Style.ID).IsEqualTo(column.ColumnName)
  351. || new Column<SupplierProduct>(x => x.Job.ID).IsEqualTo(column.ColumnName)
  352. || new Column<SupplierProduct>(x => x.Dimensions).IsEqualTo(column.ColumnName))
  353. {
  354. editor.Editable = editor.Editable.Combine(Editable.Disabled);
  355. }
  356. };
  357. }
  358. private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
  359. {
  360. if (row is null) return;
  361. column.AddItem("New Supplier", null, row =>
  362. {
  363. if (row is null) return;
  364. var selection = new MultiSelectDialog<Supplier>(
  365. new Filter<Supplier>(x => x.ID).NotInList(Suppliers.Select(x => x.ID).ToArray()),
  366. new Columns<Supplier>(x => x.ID).Add(x => x.Code), multiselect: false);
  367. if (selection.ShowDialog() != true)
  368. {
  369. return;
  370. }
  371. var supplier = selection.Data().Rows.First().ToObject<Supplier>();
  372. var productInstance = LoadItem(row);
  373. var supplierProduct = new SupplierProduct();
  374. supplierProduct.Product.CopyFrom(productInstance.Product);
  375. supplierProduct.Style.CopyFrom(productInstance.Style);
  376. supplierProduct.Dimensions.CopyFrom(productInstance.Dimensions);
  377. supplierProduct.SupplierLink.CopyFrom(supplier);
  378. if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
  379. {
  380. SupplierProducts.Add(supplierProduct);
  381. var newSuppliers = new SupplierLink[Suppliers.Length + 1];
  382. var newIdx = Suppliers.Length;
  383. for (int i = 0; i < Suppliers.Length; i++)
  384. {
  385. newSuppliers[i] = Suppliers[i];
  386. }
  387. newSuppliers[newIdx] = supplierProduct.SupplierLink;
  388. foreach (var (itemIdx, item) in Items.WithIndex())
  389. {
  390. var populateSupplierProduct = GetSupplierProduct(item);
  391. var quantities = new StockForecastOrderingItemQuantity[newSuppliers.Length];
  392. for (int i = 0; i < Suppliers.Length; ++i)
  393. {
  394. quantities[i] = item.GetQuantity(i);
  395. }
  396. var newQty = CreateQuantity(itemIdx);
  397. quantities[newIdx] = newQty;
  398. if (OrderType == StockForecastOrderingType.StockOrder)
  399. {
  400. newQty.StockTotal = 0;
  401. }
  402. else
  403. {
  404. foreach (var id in item.GetJobRequiredQuantities().Keys)
  405. {
  406. newQty.JobTotals[id] = 0;
  407. }
  408. }
  409. item.SetQuantities(quantities);
  410. }
  411. Suppliers = newSuppliers;
  412. Refresh(true, true);
  413. }
  414. });
  415. }
  416. private BitmapImage? Warning_Image(CoreRow? row)
  417. {
  418. if (row is null) return _warning;
  419. var item = LoadItem(row);
  420. if(item.GetTotalQuantity(OrderType) < item.RequiredQuantity)
  421. {
  422. return _warning;
  423. }
  424. else
  425. {
  426. return null;
  427. }
  428. }
  429. protected override void ConfigureColumnGroups()
  430. {
  431. for(int idx = 0; idx < Suppliers.Length; ++idx)
  432. {
  433. GetColumnGrouping().AddGroup(Suppliers[idx].Code, QuantityColumns[idx], CostColumns[idx]);
  434. }
  435. }
  436. private void LoadJobData(IEnumerable<Guid> ids)
  437. {
  438. var neededIDs = ids.Where(x => !JobDetails.ContainsKey(x)).ToArray();
  439. if(neededIDs.Length > 0)
  440. {
  441. var details = Client.Query(
  442. new Filter<Job>(x => x.ID).InList(neededIDs),
  443. new Columns<Job>(x => x.ID)
  444. .Add(x => x.JobNumber)
  445. .Add(x => x.Name));
  446. foreach(var job in details.ToObjects<Job>())
  447. {
  448. JobDetails[job.ID] = job;
  449. }
  450. }
  451. }
  452. private class QuantityControl : ContentControl
  453. {
  454. private readonly StockForecastOrderingItem Item;
  455. private readonly int SupplierIndex;
  456. private readonly StockForecastOrderingGrid Parent;
  457. public QuantityControl(StockForecastOrderingGrid parent, StockForecastOrderingItem item, int supplierIndex, StockForecastOrderingType mode)
  458. {
  459. Parent = parent;
  460. Item = item;
  461. SupplierIndex = supplierIndex;
  462. UpdateControl(mode);
  463. }
  464. public void UpdateControl(StockForecastOrderingType mode)
  465. {
  466. var supplierProduct = Parent.GetSupplierProduct(Item, Parent.Suppliers[SupplierIndex].ID);
  467. if(supplierProduct is null)
  468. {
  469. Content = null;
  470. return;
  471. }
  472. if(mode == StockForecastOrderingType.StockOrder)
  473. {
  474. var editor = new DoubleTextBox
  475. {
  476. VerticalAlignment = VerticalAlignment.Stretch,
  477. HorizontalAlignment = HorizontalAlignment.Stretch,
  478. Background = new SolidColorBrush(Colors.LightYellow),
  479. BorderThickness = new Thickness(0.0),
  480. MinValue = 0.0,
  481. Value = Item.GetQuantity(SupplierIndex).StockTotal
  482. };
  483. editor.ValueChanged += (o, e) =>
  484. {
  485. Item.GetQuantity(SupplierIndex).StockTotal = editor.Value ?? default;
  486. };
  487. Content = editor;
  488. }
  489. else if(mode == StockForecastOrderingType.JobOrder)
  490. {
  491. var grid = new Grid();
  492. grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
  493. grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(30) });
  494. var editor = new TextBox
  495. {
  496. VerticalAlignment = VerticalAlignment.Stretch,
  497. HorizontalAlignment = HorizontalAlignment.Stretch,
  498. VerticalContentAlignment = VerticalAlignment.Center,
  499. HorizontalContentAlignment = HorizontalAlignment.Center,
  500. Background = new SolidColorBrush(Colors.White),
  501. BorderThickness = new Thickness(0.0),
  502. IsReadOnly = true,
  503. Text = string.Format("{0:F2}", Item.GetQuantity(SupplierIndex).JobTotal)
  504. };
  505. Grid.SetColumn(editor, 0);
  506. grid.Children.Add(editor);
  507. var btn = new Button
  508. {
  509. VerticalAlignment = VerticalAlignment.Stretch,
  510. VerticalContentAlignment = VerticalAlignment.Center,
  511. HorizontalAlignment = HorizontalAlignment.Stretch,
  512. Content = "..",
  513. Margin = new Thickness(1),
  514. Focusable = false
  515. };
  516. btn.SetValue(Grid.ColumnProperty, 1);
  517. btn.SetValue(Grid.RowProperty, 0);
  518. btn.Click += (o, e) =>
  519. {
  520. var qty = Item.GetQuantity(SupplierIndex);
  521. Parent.LoadJobData(qty.JobTotals.Keys);
  522. var items = qty.JobTotals.Select(x =>
  523. {
  524. var item = new StockForecastOrderingJobItem
  525. {
  526. JobID = x.Key,
  527. RequiredQuantity = Item.GetJobRequiredQuantities().GetValueOrDefault(x.Key),
  528. Quantity = x.Value
  529. };
  530. if(item.JobID == Guid.Empty)
  531. {
  532. item.Job = "General Stock";
  533. }
  534. else if(Parent.JobDetails.TryGetValue(item.JobID, out var job))
  535. {
  536. item.Job = $"{job.JobNumber}: {job.Name}";
  537. }
  538. return item;
  539. }).ToList();
  540. var window = new StockForecastOrderJobScreen();
  541. window.Items = items;
  542. if(window.ShowDialog() == true)
  543. {
  544. foreach(var item in items)
  545. {
  546. qty.JobTotals[item.JobID] = item.Quantity;
  547. }
  548. qty.DoChanged();
  549. editor.Text = string.Format("{0:F2}", Item.GetQuantity(SupplierIndex).JobTotal);
  550. }
  551. };
  552. grid.Children.Add(btn);
  553. Content = grid;
  554. }
  555. }
  556. }
  557. private List<QuantityControl> QuantityControls = [];
  558. private void InitialiseSupplierColumn(int idx)
  559. {
  560. var supplierProducts = SupplierProducts.Where(x => x.SupplierLink.ID == Suppliers[idx].ID).ToArray();
  561. var contextMenuFunc = (CoreRow[]? rows) =>
  562. {
  563. var row = rows?.FirstOrDefault();
  564. if (row is null) return null;
  565. var item = LoadItem(row);
  566. var supplierProduct = GetSupplierProduct(item, Suppliers[idx].ID);
  567. if (supplierProduct is not null)
  568. {
  569. return null;
  570. }
  571. var menu = new ContextMenu();
  572. menu.AddItem("Create Supplier Product", null, new Tuple<StockForecastOrderingItem, int>(item, idx), CreateSupplierProduct_Click);
  573. return menu;
  574. };
  575. // Making local copy of index so that the lambda can use it, and not the changed value of 'i'.
  576. var qtyColumn = new Tuple<DynamicActionColumn, QuantityControl?>(null!, null);
  577. QuantityColumns[idx] = new DynamicTemplateColumn(row =>
  578. {
  579. var instance = LoadItem(row);
  580. var control = new QuantityControl(this, instance, idx, OrderType);
  581. QuantityControls.Add(control);
  582. return control;
  583. })
  584. {
  585. HeaderText = "Qty.",
  586. Width = 80,
  587. ContextMenu = contextMenuFunc
  588. };
  589. CostColumns[idx] = new DynamicTextColumn(row =>
  590. {
  591. if(row is null)
  592. {
  593. return "Cost";
  594. }
  595. var instance = LoadItem(row);
  596. var qty = OrderType == StockForecastOrderingType.StockOrder
  597. ? instance.GetQuantity(idx).StockTotal
  598. : instance.GetQuantity(idx).JobTotal;
  599. var supplierProduct = GetSupplierProduct(instance, Suppliers[idx].ID);
  600. if(supplierProduct is not null)
  601. {
  602. return $"{qty * supplierProduct.CostPrice:C2}";
  603. }
  604. else
  605. {
  606. return "";
  607. }
  608. })
  609. {
  610. HeaderText = "Cost",
  611. Width = 80,
  612. ContextMenu = contextMenuFunc,
  613. GetSummary = () =>
  614. {
  615. var i = idx * 2 + 1;
  616. var summary = new GridSummaryColumn
  617. {
  618. Format = "{Sum:C2}",
  619. SummaryType = Syncfusion.Data.SummaryType.Custom,
  620. CustomAggregate = new CostAggregate(idx, this)
  621. };
  622. return summary;
  623. }
  624. };
  625. ActionColumns.Add(QuantityColumns[idx]);
  626. ActionColumns.Add(CostColumns[idx]);
  627. }
  628. private void CreateSupplierProduct_Click(Tuple<StockForecastOrderingItem, int> tuple)
  629. {
  630. var (item, supplierIdx) = tuple;
  631. var supplierProduct = new SupplierProduct();
  632. supplierProduct.Product.CopyFrom(item.Product);
  633. supplierProduct.Style.CopyFrom(item.Style);
  634. supplierProduct.Dimensions.CopyFrom(item.Dimensions);
  635. supplierProduct.SupplierLink.CopyFrom(Suppliers[supplierIdx]);
  636. if (DynamicGridUtils.EditEntity(supplierProduct, customiseGrid: EditSupplierProductGrid))
  637. {
  638. SupplierProducts.Add(supplierProduct);
  639. InvalidateGrid();
  640. }
  641. }
  642. private static bool Matches(StockForecastOrderingItem item, SupplierProduct supplierProduct)
  643. {
  644. return item.Product.ID == supplierProduct.Product.ID
  645. && item.Style.ID == supplierProduct.Style.ID
  646. && item.Dimensions.Equals(supplierProduct.Dimensions);
  647. }
  648. private static bool Matches(ProductInstance instance, SupplierProduct supplierProduct)
  649. {
  650. return instance.Product.ID == supplierProduct.Product.ID
  651. && instance.Style.ID == supplierProduct.Style.ID
  652. && instance.Dimensions.Equals(supplierProduct.Dimensions);
  653. }
  654. private SupplierProduct? GetSupplierProduct(StockForecastOrderingItem item)
  655. {
  656. var defaultSupplierProduct = SupplierProducts.FirstOrDefault(x => x.ID == item.Product.Supplier.ID);
  657. if(defaultSupplierProduct is not null && Matches(item, defaultSupplierProduct))
  658. {
  659. return defaultSupplierProduct;
  660. }
  661. else
  662. {
  663. return SupplierProducts.FirstOrDefault(x => Matches(item, x));
  664. }
  665. }
  666. private SupplierProduct? GetSupplierProduct(ProductInstance instance, Guid supplierID)
  667. {
  668. return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(instance, x));
  669. }
  670. private SupplierProduct? GetSupplierProduct(StockForecastOrderingItem item, Guid supplierID)
  671. {
  672. return SupplierProducts.FirstOrDefault(x => x.SupplierLink.ID == supplierID && Matches(item, x));
  673. }
  674. //private double GetQuantity(SupplierProduct product)
  675. //{
  676. // var instance = ProductInstances.WithIndex().Where(x => x.Value.Product.ID == product.ID)
  677. //}
  678. private class CostAggregate : ISummaryAggregate
  679. {
  680. public double Sum { get; private set; }
  681. private int SupplierIndex;
  682. private StockForecastOrderingGrid Grid;
  683. public CostAggregate(int supplierIndex, StockForecastOrderingGrid grid)
  684. {
  685. SupplierIndex = supplierIndex;
  686. Grid = grid;
  687. }
  688. public Action<IEnumerable, string, PropertyDescriptor> CalculateAggregateFunc()
  689. {
  690. return AggregateFunc;
  691. }
  692. private void AggregateFunc(IEnumerable items, string property, PropertyDescriptor args)
  693. {
  694. if (items is IEnumerable<DataRowView> rows)
  695. {
  696. Sum = 0;
  697. foreach (var dataRow in rows)
  698. {
  699. var rowIdx = dataRow.Row.Table.Rows.IndexOf(dataRow.Row);
  700. var item = Grid.LoadItem(Grid.Data.Rows[rowIdx]);
  701. var supplierProduct = Grid.GetSupplierProduct(item, Grid.Suppliers[SupplierIndex].ID);
  702. if(supplierProduct is not null)
  703. {
  704. var qty = item.GetQuantity(SupplierIndex);
  705. Sum += qty.GetTotal(Grid.OrderType) * supplierProduct.CostPrice;
  706. }
  707. }
  708. }
  709. else
  710. {
  711. Logger.Send(LogType.Error, "", $"Attempting to calculate aggregate on invalid data type '{items.GetType()}'.");
  712. }
  713. }
  714. }
  715. }