StockForecastOrderingGrid.cs 28 KB

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