StockSummaryGrid.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Reflection;
  6. using System.Windows;
  7. using System.Windows.Controls;
  8. using System.Windows.Media;
  9. using System.Windows.Media.Imaging;
  10. using Comal.Classes;
  11. using InABox.Clients;
  12. using InABox.Configuration;
  13. using InABox.Core;
  14. using InABox.DynamicGrid;
  15. using InABox.Wpf;
  16. using InABox.WPF;
  17. using PRSDesktop.Utils;
  18. using Syncfusion.Data.Extensions;
  19. using Xceed.Wpf.Toolkit;
  20. namespace PRSDesktop;
  21. public enum StockSummaryMinimumStockBehaviour
  22. {
  23. Cumulative,
  24. Minimum
  25. }
  26. public class StockSummaryGrid : DynamicDataGrid<StockSummary>, IDataModelSource
  27. {
  28. private static readonly BitmapImage _warning = InABox.Wpf.Resources.warning.AsBitmapImage();
  29. public Guid[] GroupIDs { get; set; }
  30. public Guid[] JobIDs { get; set; }
  31. public Guid[] SupplierIDs { get; set; }
  32. private StockSummaryMinimumStockBehaviour MinStockBehaviour { get; set; }
  33. private Button OrderButton;
  34. public StockSummaryGrid() : base()
  35. {
  36. ColumnsTag = "StockSummaryGrid";
  37. GroupIDs = Array.Empty<Guid>();
  38. JobIDs = Array.Empty<Guid>();
  39. SupplierIDs = Array.Empty<Guid>();
  40. HiddenColumns.Add(x => x.Product.ID);
  41. HiddenColumns.Add(x => x.Product.Issues);
  42. HiddenColumns.Add(x => x.Product.Supplier.SupplierLink.ID);
  43. HiddenColumns.Add(x => x.Issues);
  44. HiddenColumns.Add(x => x.Style.ID);
  45. HiddenColumns.Add(x => x.Job.ID);
  46. HiddenColumns.Add(x => x.Dimensions.UnitSize);
  47. HiddenColumns.Add(x => x.Product.DefaultInstance.MinimumStockLevel);
  48. HiddenColumns.Add(x => x.Issued);
  49. HiddenColumns.Add(x => x.TotalRequired);
  50. HiddenColumns.Add(x => x.NettRequired);
  51. HiddenColumns.Add(x => x.AllStock);
  52. HiddenColumns.Add(x => x.OnOrder);
  53. HiddenColumns.Add(x => x.TotalStock);
  54. HiddenColumns.Add(x => x.BalanceAvailable);
  55. HiddenColumns.Add(x => x.Product.Image.ID);
  56. HiddenColumns.Add(x => x.Product.Image.FileName);
  57. //ActionColumns.Add(new DynamicIssuesColumn<Product>(this, CoreUtils.GetFullPropertyName<StockSummary, string>(x => x.Product.Issues, "."), LoadProducts));
  58. ActionColumns.Add(new DynamicImageColumn(Issues_Image, null)
  59. {
  60. ToolTip = Issues_Tooltip
  61. });
  62. ActionColumns.Add(new DynamicImagePreviewColumn<StockSummary>(x => x.Product.Image)
  63. { Position = DynamicActionColumnPosition.Start });
  64. OnCellDoubleClick += StockSummaryGrid_OnCellDoubleClick;
  65. MinStockBehaviour = new GlobalConfiguration<StockSummaryPanelSettings>().Load().MinimumStockBehaviour;
  66. OrderButton = AddButton("Order Stock", PRSDesktop.Resources.purchase.ToBitmapImage(), OrderStock_Click);
  67. OrderButton.IsEnabled = false;
  68. }
  69. protected override void SelectItems(CoreRow[]? rows)
  70. {
  71. base.SelectItems(rows);
  72. OrderButton.IsEnabled = rows is not null && rows.Length > 0;
  73. }
  74. private bool OrderStock_Click(Button button, CoreRow[] rows)
  75. {
  76. if(rows.Length == 0)
  77. {
  78. return false;
  79. }
  80. var items = rows.ToObjects<StockSummary>().ToArray();
  81. var order = new PurchaseOrder();
  82. order.Description = "Purchase Order created from Stock Forecast Screen";
  83. order.RaisedBy.ID = App.EmployeeID;
  84. order.IssuedBy.ID = App.EmployeeID;
  85. order.IssuedDate = DateTime.Today;
  86. var orderItems = new List<PurchaseOrderItem>();
  87. foreach(var stockSummary in items)
  88. {
  89. var orderItem = new PurchaseOrderItem();
  90. orderItem.Product.ID = stockSummary.Product.ID;
  91. orderItem.Style.ID = stockSummary.Style.ID;
  92. orderItem.Job.ID = stockSummary.Job.ID;
  93. orderItem.Dimensions.CopyFrom(stockSummary.Dimensions);
  94. orderItem.Qty = Math.Max(-stockSummary.BalanceAvailable, 0);
  95. orderItems.Add(orderItem);
  96. }
  97. LookupFactory.DoLookups<PurchaseOrderItem, Product, ProductLink>(
  98. orderItems.Select(x => new Tuple<PurchaseOrderItem, Guid>(x, x.Product.ID)),
  99. x => x.Product);
  100. LookupFactory.DoLookups<PurchaseOrderItem, ProductStyle, ProductStyleLink>(
  101. orderItems.Select(x => new Tuple<PurchaseOrderItem, Guid>(x, x.Style.ID)),
  102. x => x.Style);
  103. LookupFactory.DoLookups<PurchaseOrderItem, Job, JobLink>(
  104. orderItems.Select(x => new Tuple<PurchaseOrderItem, Guid>(x, x.Job.ID)),
  105. x => x.Job);
  106. if (DynamicGridUtils.EditEntity(order, t =>
  107. {
  108. if (t == typeof(PurchaseOrderItem))
  109. {
  110. var table = new CoreTable();
  111. table.LoadColumns(typeof(PurchaseOrderItem));
  112. table.LoadRows(orderItems);
  113. return table;
  114. }
  115. return null;
  116. }, preloadPages: true))
  117. {
  118. MessageWindow.ShowMessage("Purchase order created.", "Success.");
  119. }
  120. return false;
  121. }
  122. private BitmapImage? Issues_Image(CoreRow? row)
  123. {
  124. if (row is null) return _warning;
  125. return row.Get<StockSummary, string>(x => x.Issues).IsNullOrWhiteSpace() ? null : _warning;
  126. }
  127. private FrameworkElement? Issues_Tooltip(DynamicActionColumn column, CoreRow? row)
  128. {
  129. if (row is null) return null;
  130. return column.TextToolTip(row.Get<StockSummary, string>(x => x.Issues));
  131. }
  132. private class UIComponent : DynamicGridGridUIComponent<StockSummary>
  133. {
  134. private StockSummaryGrid Grid;
  135. public UIComponent(StockSummaryGrid grid)
  136. {
  137. Grid = grid;
  138. Parent = grid;
  139. }
  140. protected override Brush? GetCellBackground(CoreRow row, string columnname)
  141. {
  142. if (String.Equals(columnname, Grid._balance))
  143. {
  144. return row.Get<StockSummary, double>(x => x.BalanceAvailable) < 0.0F
  145. ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
  146. : null;
  147. }
  148. return null;
  149. }
  150. }
  151. protected override IDynamicGridUIComponent<StockSummary> CreateUIComponent()
  152. {
  153. return new UIComponent(this);
  154. }
  155. private Product[] LoadProducts(CoreRow[] arg)
  156. {
  157. return Client.Query(
  158. new Filter<Product>(x => x.ID).InList(arg.Select(x => x.Get<StockSummary, Guid>(x => x.Product.ID)).ToArray()),
  159. new Columns<Product>(x => x.ID, x => x.Issues))
  160. .ToObjects<Product>().ToArray();
  161. }
  162. protected override void DoReconfigure(FluentList<DynamicGridOption> options)
  163. {
  164. base.DoReconfigure(options);
  165. options.AddRange(
  166. DynamicGridOption.RecordCount,
  167. DynamicGridOption.SelectColumns,
  168. DynamicGridOption.FilterRows,
  169. DynamicGridOption.ExportData,
  170. DynamicGridOption.MultiSelect
  171. );
  172. }
  173. public override DynamicGridColumns GenerateColumns()
  174. {
  175. var columns = new DynamicGridColumns();
  176. columns.Add<StockSummary, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
  177. columns.Add<StockSummary, string>(x => x.Style.Code, 120, "Style Code", "", Alignment.MiddleCenter);
  178. columns.Add<StockSummary, string>(x => x.Dimensions.UnitSize, 120, "Unit Size", "", Alignment.MiddleCenter);
  179. columns.Add<StockSummary, int>(x => x.MinimumStockLevel, 120, "Minimum Stock Level", "", Alignment.MiddleCenter);
  180. columns.Add<StockSummary, double>(x => x.BillOfMaterials, 120, "BOM", "", Alignment.MiddleCenter);
  181. columns.Add<StockSummary, double>(x => x.Issued, 120, "Issued", "", Alignment.MiddleCenter);
  182. columns.Add<StockSummary, double>(x => x.AllStock, 120, "Stock", "", Alignment.MiddleCenter);
  183. columns.Add<StockSummary, double>(x => x.OnOrder, 120, "On Order", "", Alignment.MiddleCenter);
  184. columns.Add<StockSummary, double>(x => x.BalanceAvailable, 120, "Balance Available", "", Alignment.MiddleCenter);
  185. return columns;
  186. }
  187. private void ShowDetailGrid<TEntity>(
  188. String columnname,
  189. Expression<Func<TEntity,object?>> productcol,
  190. Guid productid,
  191. Expression<Func<TEntity,object?>> stylecol,
  192. Guid? styleid,
  193. Expression<Func<TEntity,object?>> unitcol,
  194. String unitsize,
  195. Expression<Func<TEntity,object?>>? jobcol,
  196. Filter<TEntity>? extrafilter,
  197. Func<CoreRow,bool>? rowfilter
  198. )
  199. {
  200. var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
  201. if (grid == null)
  202. {
  203. MessageWindow.ShowError($"Cannot create Grid for [{typeof(TEntity).Name}]", "", shouldLog: false);
  204. return;
  205. }
  206. grid.ColumnsTag = $"{ColumnsTag}.{columnname}";
  207. grid.Reconfigure(options => { options.BeginUpdate().Clear().AddRange(DynamicGridOption.FilterRows, DynamicGridOption.SelectColumns).EndUpdate(); });
  208. grid.OnDefineFilter += t =>
  209. {
  210. var filter = new Filter<TEntity>(productcol).IsEqualTo(productid)
  211. .And(unitcol).IsEqualTo(unitsize);
  212. if (styleid.HasValue)
  213. filter = filter.And(stylecol).IsEqualTo(styleid);
  214. if (jobcol != null)
  215. filter = filter.And(new Filter<TEntity>(jobcol).InList(JobIDs).Or(jobcol).IsEqualTo(Guid.Empty));
  216. if (extrafilter != null)
  217. filter = filter.And(extrafilter);
  218. return filter;
  219. };
  220. grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
  221. var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(columnname)} Calculation", grid);
  222. window.ShowDialog();
  223. }
  224. private void StockSummaryGrid_OnCellDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  225. {
  226. Guid productid = args.Row.Get<StockSummary, Guid>(c => c.Product.ID);
  227. Guid? styleid = HasStyle() ? args.Row.Get<StockSummary, Guid>(c => c.Style.ID) : null;
  228. String unitsize = args.Row.Get<StockSummary, String>(c => c.Dimensions.UnitSize);
  229. if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".")))
  230. {
  231. ShowDetailGrid<JobBillOfMaterialsItem>(
  232. args.Column.ColumnName,
  233. x => x.Product.ID,
  234. productid,
  235. x => x.Style.ID,
  236. styleid,
  237. x=>x.Dimensions.UnitSize,
  238. unitsize,
  239. x => x.Job.ID,
  240. new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
  241. null
  242. );
  243. }
  244. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, int>(x => x.MinimumStockLevel, ".")))
  245. {
  246. ShowDetailGrid<ProductInstance>(
  247. args.Column.ColumnName,
  248. x => x.Product.ID,
  249. productid,
  250. x => x.Style.ID,
  251. styleid,
  252. x => x.Dimensions.UnitSize,
  253. unitsize,
  254. null,
  255. null,
  256. null
  257. );
  258. }
  259. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".")))
  260. {
  261. ShowDetailGrid<StockMovement>(
  262. args.Column.ColumnName,
  263. x => x.Product.ID,
  264. productid,
  265. x => x.Style.ID,
  266. styleid,
  267. x=>x.Dimensions.UnitSize,
  268. unitsize,
  269. x => x.Job.ID,
  270. new Filter<StockMovement>(x => x.Type).IsEqualTo(StockMovementType.Issue),
  271. null
  272. );
  273. }
  274. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalRequired, ".")))
  275. {
  276. ShowDetailGrid<StockSummary>(
  277. args.Column.ColumnName,
  278. x => x.Product.ID,
  279. productid,
  280. x => x.Style.ID,
  281. styleid,
  282. x=>x.Dimensions.UnitSize,
  283. unitsize,
  284. x => x.Job.ID,
  285. null,
  286. null
  287. );
  288. }
  289. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".")))
  290. {
  291. ShowDetailGrid<StockSummary>(
  292. args.Column.ColumnName,
  293. x => x.Product.ID,
  294. productid,
  295. x => x.Style.ID,
  296. styleid,
  297. x=>x.Dimensions.UnitSize,
  298. unitsize,
  299. x => x.Job.ID,
  300. null,
  301. null
  302. );
  303. }
  304. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".")))
  305. {
  306. ShowDetailGrid<StockHolding>(
  307. args.Column.ColumnName,
  308. x => x.Product.ID,
  309. productid,
  310. x => x.Style.ID,
  311. styleid,
  312. x=>x.Dimensions.UnitSize,
  313. unitsize,
  314. null,
  315. StockHoldingFilter(),
  316. (row) => row.Get<StockHolding,double>(x=>x.Units) != 0.0F
  317. );
  318. }
  319. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".")))
  320. {
  321. ShowDetailGrid<PurchaseOrderItem>(
  322. args.Column.ColumnName,
  323. x => x.Product.ID,
  324. productid,
  325. x => x.Style.ID,
  326. styleid,
  327. x=>x.Dimensions.UnitSize,
  328. unitsize,
  329. null,
  330. PurchaseOrderItemFilter(),
  331. null
  332. );
  333. }
  334. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalStock, ".")))
  335. {
  336. ShowDetailGrid<StockSummary>(
  337. args.Column.ColumnName,
  338. x => x.Product.ID,
  339. productid,
  340. x => x.Style.ID,
  341. styleid,
  342. x=>x.Dimensions.UnitSize,
  343. unitsize,
  344. x => x.Job.ID,
  345. null,
  346. null
  347. );
  348. }
  349. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".")))
  350. {
  351. ShowDetailGrid<StockSummary>(
  352. args.Column.ColumnName,
  353. x => x.Product.ID,
  354. productid,
  355. x => x.Style.ID,
  356. styleid,
  357. x=>x.Dimensions.UnitSize,
  358. unitsize,
  359. x => x.Job.ID,
  360. null,
  361. null
  362. );
  363. }
  364. }
  365. private bool HasStyle()
  366. {
  367. return DataColumns().ColumnNames().Any(x => x.StartsWith("Style.") && !x.Equals("Style.ID"));
  368. }
  369. private Filters<StockSummary> GetFilters()
  370. {
  371. var filters = new Filters<StockSummary>();
  372. filters.Add(FilterComponent.GetFilter());
  373. Filter<StockSummary>? _groupfilter = !GroupIDs.Any()
  374. ? new Filter<StockSummary>().None()
  375. : !GroupIDs.Contains(CoreUtils.FullGuid)
  376. ? new Filter<StockSummary>(x => x.Product.Group.ID).InList(GroupIDs)
  377. : null;
  378. filters.Add(_groupfilter);
  379. var jobFilter = !JobIDs.Any()
  380. ? new Filter<StockSummary>().None()
  381. : new Filter<StockSummary>(x => x.Job.ID).InList(JobIDs);
  382. filters.Add(jobFilter.Or(x => x.Job.ID).IsEqualTo(Guid.Empty));
  383. var supplierFilter = !SupplierIDs.Any()
  384. ? new Filter<StockSummary>().None()
  385. : new Filter<StockSummary>(x => x.Product.Supplier.SupplierLink.ID).InList(SupplierIDs);
  386. filters.Add(supplierFilter.Or(x => x.Product.Supplier.SupplierLink.ID).IsEqualTo(Guid.Empty));
  387. //Filter<StockSummary> _productfilter = new Filter<StockSummary>(x => x.Product.ID).IsNotEqualTo(Guid.Empty);
  388. //filters.Add(_productfilter);
  389. return filters;
  390. }
  391. private Tuple<Guid,Guid?,String>[] GetKeys(IEnumerable<CoreRow> rows, Columns<StockSummary> columns, bool hasstyle)
  392. {
  393. int productcol = columns.IndexOf(x => x.Product.ID);
  394. int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  395. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  396. var result = rows.Select(r => new Tuple<Guid, Guid?, String>(
  397. (Guid)(r.Values[productcol] ?? Guid.Empty),
  398. (stylecol != -1) ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null,
  399. (String)(r.Values[unitcol] ?? ""))
  400. ).Distinct().ToArray();
  401. return result;
  402. }
  403. private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid productid, Guid? styleid, String unitsize) where TSource : IJobMaterial
  404. {
  405. int productcol = columns.IndexOf(x => x.Product.ID);
  406. int stylecol = styleid.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
  407. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  408. var subset = rows
  409. .Where(r=>
  410. Guid.Equals(r.Values[productcol], productid)
  411. && (!styleid.HasValue || Guid.Equals(r.Values[stylecol], styleid))
  412. && String.Equals(r.Values[unitcol], unitsize)
  413. );
  414. return subset.ToArray();
  415. }
  416. private double Aggregate<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, bool hasstyle, bool hasjob, Expression<Func<TSource, object>> source, CoreRow target, Expression<Func<StockSummary, object>> aggregate )
  417. {
  418. int srcol = columns.IndexOf(source);
  419. if (srcol == -1)
  420. return 0.00;
  421. var total = rows.Aggregate(0d, (value, row) => value + (double)(row.Values[srcol] ?? 0.0d));
  422. // int productcol = columns.IndexOf(x => x.Product.ID);
  423. // int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  424. // int jobcol = hasjob ? columns.IndexOf(x => x.Job.ID) : -1;
  425. // int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  426. //
  427. // var tuples = rows.Select(r => new Tuple<Guid, Guid?, Guid?, String, double>(
  428. // (Guid)(r.Values[productcol] ?? Guid.Empty),
  429. // (hasstyle ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null),
  430. // (hasjob ? (Guid)(r.Values[jobcol] ?? Guid.Empty) : null),
  431. // (String)(r.Values[unitcol] ?? ""),
  432. // (double)(r.Values[aggcol] ?? 0.0d))
  433. // ).ToArray();
  434. //
  435. // var total = tuples.Aggregate(0d, (value, tuple) => value + tuple.Item5);
  436. target.Set(aggregate, total);
  437. return total;
  438. }
  439. private Filter<StockHolding> StockHoldingFilter()
  440. {
  441. var stockHoldingFilter = new Filter<StockHolding>(x => x.Job.ID).InList(JobIDs)
  442. .Or(x => x.Job.ID).IsEqualTo(Guid.Empty);
  443. return new Filter<StockHolding>(x => x.Units).IsNotEqualTo(0.0)
  444. .And(stockHoldingFilter);
  445. }
  446. private Filter<PurchaseOrderItem> PurchaseOrderItemFilter()
  447. {
  448. var poJobFilter = new Filter<PurchaseOrderItem>(x => x.Job.ID).InList(JobIDs)
  449. .Or(x => x.Job.ID).IsEqualTo(Guid.Empty);
  450. return new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue)
  451. .And(poJobFilter);
  452. }
  453. protected override void Reload(Filters<StockSummary> criteria, Columns<StockSummary> columns, ref SortOrder<StockSummary>? sort,
  454. Action<CoreTable?, Exception?> action)
  455. {
  456. var filter = GetFilters().Combine();
  457. new Client<StockSummary>().Query(filter,columns,sort, (o,e) =>
  458. {
  459. if(o is null)
  460. {
  461. Dispatcher.BeginInvoke(() =>
  462. {
  463. MessageWindow.ShowError("Failed to load data", e);
  464. });
  465. return;
  466. }
  467. var pids = o.ExtractValues<StockSummary, Guid>(x => x.Product.ID).ToArray();
  468. MultiQuery query = new MultiQuery();
  469. query.Add<StockHolding>(
  470. new Filter<StockHolding>(x => x.Product.ID).InList(pids)
  471. .And(StockHoldingFilter()),
  472. new Columns<StockHolding>(x => x.Product.ID)
  473. .Add(x => x.Style.ID)
  474. .Add(x => x.Dimensions.UnitSize)
  475. .Add(x => x.Units)
  476. );
  477. query.Add<PurchaseOrderItem>(
  478. new Filter<PurchaseOrderItem>(x => x.Product.ID).InList(pids)
  479. .And(PurchaseOrderItemFilter()),
  480. new Columns<PurchaseOrderItem>(x => x.Product.ID)
  481. .Add(x => x.Style.ID)
  482. .Add(x => x.Dimensions.UnitSize)
  483. .Add(x => x.Qty)
  484. );
  485. query.Query();
  486. var holdings = query.Get<StockHolding>();
  487. var holdingcolumns = new Columns<StockHolding>(holdings.Columns.Select(x => x.ColumnName));
  488. var orders = query.Get<PurchaseOrderItem>();
  489. var ordercolumns = new Columns<PurchaseOrderItem>(orders.Columns.Select(x => x.ColumnName));
  490. var table = new CoreTable();
  491. table.LoadColumns(columns);
  492. if (o != null)
  493. {
  494. bool bHasStyle = HasStyle();
  495. var keys = GetKeys(o.Rows, columns, bHasStyle);
  496. foreach (var key in keys)
  497. {
  498. var rows = GetRows(o.Rows, columns, key.Item1, key.Item2, key.Item3);
  499. if (rows.Any())
  500. {
  501. CoreRow newrow = table.NewRow();
  502. newrow.LoadValues(rows.First().Values);
  503. Aggregate(rows, columns, bHasStyle, true, x => x.BillOfMaterials, newrow, x => x.BillOfMaterials);
  504. Aggregate(rows, columns, bHasStyle, true, x => x.Issued, newrow, x => x.Issued);
  505. Aggregate(rows, columns, bHasStyle, true, x => x.TotalRequired, newrow, x => x.TotalRequired);
  506. var nettrequired = Aggregate(rows, columns, bHasStyle, true, x => x.NettRequired, newrow, x => x.NettRequired);
  507. var holdingrows = GetRows(holdings.Rows, holdingcolumns, key.Item1, key.Item2, key.Item3);
  508. var allstock = Aggregate(holdingrows, holdingcolumns, bHasStyle, false, x => x.Units, newrow, x => x.AllStock);
  509. var orderrows = GetRows(orders.Rows, ordercolumns, key.Item1, key.Item2, key.Item3);
  510. var onorder = Aggregate(orderrows, ordercolumns, bHasStyle, false, x => x.Qty, newrow, x => x.OnOrder);
  511. newrow.Set<StockSummary, double>(x => x.TotalStock, allstock + onorder);
  512. var minStock = newrow.Get<StockSummary, double>(c => c.MinimumStockLevel);
  513. var balance = MinStockBehaviour switch
  514. {
  515. StockSummaryMinimumStockBehaviour.Cumulative => allstock + onorder -
  516. (minStock + nettrequired),
  517. StockSummaryMinimumStockBehaviour.Minimum or _ => allstock + onorder -
  518. Math.Max(minStock, nettrequired),
  519. };
  520. newrow.Set<StockSummary, double>(x => x.BalanceAvailable, balance);
  521. var productIssues = rows.First().Get<StockSummary, string>(x => x.Product.Issues);
  522. var issues = new List<string>();
  523. if(balance < 0)
  524. {
  525. issues.Add("Not enough stock available.");
  526. }
  527. if (!productIssues.IsNullOrWhiteSpace())
  528. {
  529. issues.Add(productIssues);
  530. }
  531. newrow.Set<StockSummary, string>(x => x.Issues, string.Join('\n', issues));
  532. table.Rows.Add(newrow);
  533. }
  534. }
  535. }
  536. action?.Invoke(table, e);
  537. });
  538. }
  539. protected override bool FilterRecord(CoreRow row)
  540. {
  541. var result = base.FilterRecord(row);
  542. if (result)
  543. result = (result && (
  544. row.Get<StockSummary, double>(x => x.BillOfMaterials) != 0.0F
  545. || row.Get<StockSummary, double>(x => x.MinimumStockLevel) != 0.0F
  546. || row.Get<StockSummary, double>(x => x.Issued) != 0.0F
  547. || row.Get<StockSummary, double>(x => x.AllStock) != 0.0F
  548. || row.Get<StockSummary, double>(x => x.OnOrder) != 0.0F
  549. )
  550. );
  551. return result;
  552. }
  553. // private String _minstock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Product.MinimumStockLevel, ".");
  554. // private String _bom = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".");
  555. // private String _issued = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".");
  556. // private String _nettrequired = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".");
  557. // private String _stock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".");
  558. // private String _ordered = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".");
  559. private string _balance = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".");
  560. #region IDataModelSource
  561. public event DataModelUpdateEvent? OnUpdateDataModel;
  562. public string SectionName => "Stock Summary";
  563. public DataModel DataModel(Selection selection)
  564. {
  565. return new AutoDataModel<StockSummary>(null);
  566. }
  567. #endregion
  568. }