StockForecastOrderingGrid.cs 28 KB

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