StockSummaryGrid.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Media;
  8. using System.Windows.Media.Imaging;
  9. using Comal.Classes;
  10. using InABox.Core;
  11. using InABox.DynamicGrid;
  12. using InABox.Wpf;
  13. using InABox.WPF;
  14. using Newtonsoft.Json;
  15. namespace PRSDesktop;
  16. public class StockSummaryGrid : DynamicDataGrid<ProductInstance>, IDataModelSource
  17. {
  18. private enum ColumnTag
  19. {
  20. MinimumStockRequired,
  21. GeneralStockHoldings,
  22. GeneralPurchaseOrders,
  23. JobStockRequired,
  24. JobStockHoldings,
  25. JobPurchaseOrders,
  26. BalanceRequired
  27. }
  28. private CoreTable? _stockHoldings = null;
  29. private CoreTable? _poItems = null;
  30. private CoreTable? _jobBOMs = null;
  31. private CoreTable? _stockMovements = null;
  32. private CoreTable? _supplierProducts = null;
  33. private static readonly BitmapImage _warning = InABox.Wpf.Resources.warning.AsBitmapImage();
  34. public Guid[] GroupIDs { get; set; } = [];
  35. public Guid[] JobIDs { get; set; } = [];
  36. public HashSet<Guid> SupplierIDs { get; set; } = [];
  37. private readonly Button? OrderButton;
  38. public StockSummaryGrid() : base()
  39. {
  40. ColumnsTag = "StockSummaryGrid";
  41. HiddenColumns.Add(x=>x.ID);
  42. HiddenColumns.Add(x => x.Product.ID);
  43. HiddenColumns.Add(x => x.Product.Issues);
  44. HiddenColumns.Add(x => x.Style.ID);
  45. HiddenColumns.Add(x => x.Dimensions.UnitSize);
  46. HiddenColumns.Add(x => x.Product.Image.ID);
  47. HiddenColumns.Add(x => x.Product.Image.FileName);
  48. HiddenColumns.Add(x=>x.Product.Supplier.SupplierLink.ID);
  49. HiddenColumns.Add(x=>x.MinimumStockLevel);
  50. ActionColumns.Add(new DynamicImageColumn(Issues_Image, null)
  51. {
  52. ToolTip = Issues_Tooltip,
  53. Position = DynamicActionColumnPosition.Start
  54. });
  55. ActionColumns.Add(new DynamicImagePreviewColumn<StockSummary>(x => x.Product.Image)
  56. {
  57. Position = DynamicActionColumnPosition.Start
  58. });
  59. CreateColumn(GetMinimumStockLevel, ColumnTag.MinimumStockRequired,"Min Stk.","F2");
  60. CreateColumn(GetGeneralStockLevel, ColumnTag.GeneralStockHoldings,"Gen Hld.","F2");
  61. CreateColumn(GetGeneralPurchaseOrder, ColumnTag.GeneralPurchaseOrders, "Gen PO.","F2");
  62. CreateColumn(GetBOMBalance, ColumnTag.JobStockRequired, "Job BOM.","F2");
  63. CreateColumn(GetReservedStock, ColumnTag.JobStockHoldings, "Job Hld.","F2");
  64. CreateColumn(GetReservedPurchaseOrder, ColumnTag.JobPurchaseOrders, "Job PO.","F2");
  65. CreateColumn(GetBalanceRequired, ColumnTag.BalanceRequired,"Required","");
  66. OrderButton = AddButton("Order Stock", PRSDesktop.Resources.purchase.ToBitmapImage(), OrderStock_Click);
  67. OrderButton.IsEnabled = false;
  68. }
  69. private void CreateColumn(DynamicTextColumn.GetTextDelegate calculate, ColumnTag tag, string header, string format)
  70. {
  71. var column = new DynamicTextColumn(calculate)
  72. {
  73. Width = 60,
  74. Format=format,
  75. Position = DynamicActionColumnPosition.End,
  76. Tag = tag,
  77. HeaderText = header
  78. };
  79. ActionColumns.Add(column);
  80. }
  81. protected override void SelectItems(CoreRow[]? rows)
  82. {
  83. base.SelectItems(rows);
  84. OrderButton.IsEnabled = rows is not null && rows.Length > 0;
  85. }
  86. private BitmapImage? Issues_Image(CoreRow? row)
  87. {
  88. return (row is null)
  89. ? _warning
  90. : row.Get<ProductInstance, string>(x => x.Product.Issues).IsNullOrWhiteSpace()
  91. ? null
  92. : _warning;
  93. }
  94. private FrameworkElement? Issues_Tooltip(DynamicActionColumn column, CoreRow? row)
  95. {
  96. return (row is null)
  97. ? null
  98. : column.TextToolTip(row.Get<ProductInstance, string>(x => x.Product.Issues));
  99. }
  100. private UIComponent? _uicomponent = null;
  101. private class UIComponent : DynamicGridGridUIComponent<ProductInstance>
  102. {
  103. private StockSummaryGrid Grid;
  104. public UIComponent(StockSummaryGrid grid)
  105. {
  106. Grid = grid;
  107. Parent = grid;
  108. }
  109. private int? _instanceProductIDCol;
  110. private int? _instanceStyleIDCol;
  111. private int? _instanceUnitSizeCol;
  112. private int? _instanceSupplierCol;
  113. private int? _supplierProductIDCol;
  114. private int? _supplierStyleIDCol;
  115. private int? _supplierUnitSizeCol;
  116. private int? _supplierSupplierCol;
  117. public bool CheckSuppliers(CoreRow row)
  118. {
  119. if (Grid._supplierProducts == null)
  120. return false;
  121. _supplierProductIDCol ??=
  122. new Columns<SupplierProduct>(Grid._supplierProducts.Columns).IndexOf(x => x.Product.ID);
  123. _supplierStyleIDCol ??=
  124. new Columns<SupplierProduct>(Grid._supplierProducts.Columns).IndexOf(x => x.Style.ID);
  125. _supplierUnitSizeCol ??=
  126. new Columns<SupplierProduct>(Grid._supplierProducts.Columns).IndexOf(x => x.Dimensions.UnitSize);
  127. _supplierSupplierCol ??=
  128. new Columns<SupplierProduct>(Grid._supplierProducts.Columns).IndexOf(x => x.SupplierLink.ID);
  129. _instanceProductIDCol ??= new Columns<ProductInstance>(row.Table.Columns).IndexOf(x => x.Product.ID);
  130. _instanceStyleIDCol ??= new Columns<ProductInstance>(row.Table.Columns).IndexOf(x => x.Style.ID);
  131. _instanceUnitSizeCol ??= new Columns<ProductInstance>(row.Table.Columns).IndexOf(x => x.Dimensions.UnitSize);
  132. return Grid._supplierProducts.Rows.Any(r =>
  133. Equals(r.Values[_supplierProductIDCol.Value], row.Values[_instanceProductIDCol.Value])
  134. && Equals(r.Values[_supplierStyleIDCol.Value], row.Values[_instanceStyleIDCol.Value])
  135. && Equals(r.Values[_supplierUnitSizeCol.Value], row.Values[_instanceUnitSizeCol.Value])
  136. && Grid.SupplierIDs.Contains((Guid?)r.Values[_supplierSupplierCol.Value] ?? Guid.Empty));
  137. }
  138. protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
  139. {
  140. _instanceProductIDCol ??= new Columns<ProductInstance>(row.Table.Columns).IndexOf(x => x.Product.ID);
  141. _instanceStyleIDCol ??= new Columns<ProductInstance>(row.Table.Columns).IndexOf(x => x.Style.ID);
  142. _instanceUnitSizeCol ??= new Columns<ProductInstance>(row.Table.Columns).IndexOf(x => x.Dimensions.UnitSize);
  143. if (Grid.AllStock && !CheckSuppliers(row))
  144. return new SolidColorBrush(Colors.Silver) { Opacity = 0.5F };
  145. if (column is DynamicTextColumn col && Grid._summaryinfo.TryGetValue(row.Get<ProductInstance,Guid>(x=>x.ID), out StockSummaryInfo? info))
  146. {
  147. var stock = Math.Max(0.0F, info.MinStock - (info.GenStock + info.GenPO)).IsEffectivelyEqual(0.0F)
  148. ? new SolidColorBrush(Colors.LightBlue) { Opacity = 0.5 }
  149. : new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 };
  150. var job = Math.Max(0.0F, info.JobBOM - (info.JobStock + info.JobPO)).IsEffectivelyEqual(0.0F)
  151. ? new SolidColorBrush(Colors.LightGreen) { Opacity = 0.5 }
  152. : new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 };
  153. var overall = !(Grid.Optimise ? info.Optimised : info.Required).IsEffectivelyEqual(0.0F)
  154. ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
  155. : null;
  156. return col.Tag switch
  157. {
  158. ColumnTag.MinimumStockRequired => stock,
  159. ColumnTag.GeneralStockHoldings => stock,
  160. ColumnTag.GeneralPurchaseOrders => stock,
  161. ColumnTag.JobStockRequired => job,
  162. ColumnTag.JobStockHoldings => job,
  163. ColumnTag.JobPurchaseOrders => job,
  164. ColumnTag.BalanceRequired => overall,
  165. _ => null
  166. };
  167. }
  168. return null;
  169. }
  170. }
  171. protected override IDynamicGridUIComponent<ProductInstance> CreateUIComponent()
  172. {
  173. return _uicomponent ??= new UIComponent(this);
  174. }
  175. protected override void DoReconfigure(FluentList<DynamicGridOption> options)
  176. {
  177. base.DoReconfigure(options);
  178. options
  179. .BeginUpdate()
  180. .Clear()
  181. .Add(DynamicGridOption.RecordCount)
  182. .Add(DynamicGridOption.SelectColumns)
  183. .Add(DynamicGridOption.FilterRows)
  184. .Add(DynamicGridOption.ExportData)
  185. .Add(DynamicGridOption.MultiSelect)
  186. .EndUpdate();
  187. }
  188. public override DynamicGridColumns GenerateColumns()
  189. {
  190. var columns = new DynamicGridColumns();
  191. columns.Add<ProductInstance, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
  192. columns.Add<ProductInstance, string>(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleLeft);
  193. columns.Add<ProductInstance, string>(x => x.Style.Code, 120, "Style Code", "", Alignment.MiddleCenter);
  194. columns.Add<ProductInstance, string>(x => x.Dimensions.UnitSize, 120, "Unit Size", "", Alignment.MiddleCenter);
  195. return columns;
  196. }
  197. private void ShowDetailGrid<TEntity>(
  198. String tag,
  199. Expression<Func<TEntity,object?>> productcol,
  200. Guid productid,
  201. Expression<Func<TEntity,object?>> stylecol,
  202. Guid? styleid,
  203. Expression<Func<TEntity,object?>> unitcol,
  204. String unitsize,
  205. Expression<Func<TEntity,object?>>? jobcol,
  206. Filter<TEntity>? extrafilter,
  207. Func<CoreRow,bool>? rowfilter
  208. )
  209. {
  210. var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
  211. if (grid == null)
  212. {
  213. MessageWindow.ShowError($"Cannot create Grid for [{typeof(TEntity).Name}]", "", shouldLog: false);
  214. return;
  215. }
  216. grid.ColumnsTag = $"{ColumnsTag}.{tag}";
  217. grid.Reconfigure(options => { options.BeginUpdate().Clear().AddRange(DynamicGridOption.FilterRows, DynamicGridOption.SelectColumns).EndUpdate(); });
  218. grid.OnDefineFilter += t =>
  219. {
  220. var filter = new Filter<TEntity>(productcol).IsEqualTo(productid)
  221. .And(unitcol).IsEqualTo(unitsize);
  222. if (styleid.HasValue)
  223. filter = filter.And(stylecol).IsEqualTo(styleid);
  224. if (jobcol != null)
  225. filter = filter.And(new Filter<TEntity>(jobcol).InList(JobIDs));
  226. if (extrafilter != null)
  227. filter = filter.And(extrafilter);
  228. return filter;
  229. };
  230. grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
  231. var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(tag)} Calculation", grid);
  232. window.ShowDialog();
  233. }
  234. protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  235. {
  236. //base.DoDoubleClick(sender, args);
  237. Guid productid = args?.Row?.Get<StockSummary, Guid>(c => c.Product.ID) ?? Guid.Empty;
  238. Guid? styleid = HasStyle() ? args?.Row?.Get<StockSummary, Guid>(c => c.Style.ID) : null;
  239. String unitsize = args?.Row?.Get<StockSummary, String>(c => c.Dimensions.UnitSize) ?? "";
  240. if (Equals(args?.Column?.Tag, ColumnTag.GeneralStockHoldings))
  241. {
  242. ShowDetailGrid<StockHolding>(
  243. ColumnTag.GeneralStockHoldings.ToString(),
  244. x => x.Product.ID,
  245. productid,
  246. x => x.Style.ID,
  247. styleid,
  248. x=>x.Dimensions.UnitSize,
  249. unitsize,
  250. null,
  251. new Filter<StockHolding>(x=>x.Job.ID).IsEqualTo(Guid.Empty),
  252. null
  253. );
  254. }
  255. else if (Equals(args?.Column?.Tag, ColumnTag.GeneralPurchaseOrders))
  256. {
  257. ShowDetailGrid<PurchaseOrderItem>(
  258. ColumnTag.GeneralPurchaseOrders.ToString(),
  259. x => x.Product.ID,
  260. productid,
  261. x => x.Style.ID,
  262. styleid,
  263. x=>x.Dimensions.UnitSize,
  264. unitsize,
  265. null,
  266. new Filter<PurchaseOrderItem>(x=>x.Job.ID).IsEqualTo(Guid.Empty)
  267. .And(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue),
  268. null
  269. );
  270. }
  271. else if (Equals(args?.Column?.Tag, ColumnTag.JobStockRequired))
  272. {
  273. ShowDetailGrid<JobBillOfMaterialsItem>(
  274. ColumnTag.JobStockRequired.ToString(),
  275. x => x.Product.ID,
  276. productid,
  277. x => x.Style.ID,
  278. styleid,
  279. x=>x.Dimensions.UnitSize,
  280. unitsize,
  281. x => x.Job.ID,
  282. new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
  283. null
  284. );
  285. }
  286. else if (Equals(args?.Column?.Tag, ColumnTag.JobStockHoldings))
  287. {
  288. ShowDetailGrid<StockHolding>(
  289. ColumnTag.JobStockHoldings.ToString(),
  290. x => x.Product.ID,
  291. productid,
  292. x => x.Style.ID,
  293. styleid,
  294. x=>x.Dimensions.UnitSize,
  295. unitsize,
  296. x => x.Job.ID,
  297. null,
  298. null
  299. );
  300. }
  301. else if (Equals(args?.Column?.Tag, ColumnTag.JobPurchaseOrders))
  302. {
  303. ShowDetailGrid<PurchaseOrderItem>(
  304. ColumnTag.GeneralPurchaseOrders.ToString(),
  305. x => x.Product.ID,
  306. productid,
  307. x => x.Style.ID,
  308. styleid,
  309. x=>x.Dimensions.UnitSize,
  310. unitsize,
  311. x=>x.Job.ID,
  312. new Filter<PurchaseOrderItem>(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue),
  313. null
  314. );
  315. }
  316. }
  317. private bool HasStyle()
  318. {
  319. return DataColumns().ColumnNames().Any(x => x.StartsWith("Style.") && !x.Equals("Style.ID"));
  320. }
  321. private Tuple<Guid,Guid?,String>[] GetKeys(IEnumerable<CoreRow> rows, Columns<ProductInstance> columns, bool hasstyle)
  322. {
  323. int productcol = columns.IndexOf(x => x.Product.ID);
  324. int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  325. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  326. var result = rows.Select(r => new Tuple<Guid, Guid?, String>(
  327. (Guid)(r.Values[productcol] ?? Guid.Empty),
  328. (stylecol != -1) ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null,
  329. (String)(r.Values[unitcol] ?? ""))
  330. ).Distinct().ToArray();
  331. return result;
  332. }
  333. private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid productid, Guid? styleid, string unitsize, Guid[] jobids) where TSource : IJobMaterial
  334. {
  335. int productcol = columns.IndexOf(x => x.Product.ID);
  336. int stylecol = styleid.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
  337. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  338. int jobcol = columns.IndexOf(x => x.Job.ID);
  339. var subset = rows
  340. .Where(r=>
  341. Guid.Equals(r.Values[productcol], productid)
  342. && (!styleid.HasValue || Guid.Equals(r.Values[stylecol], styleid))
  343. && String.Equals(r.Values[unitcol], unitsize)
  344. && jobids.Any(x=>Equals(x,r.Values[jobcol]))
  345. );
  346. return subset.ToArray();
  347. }
  348. private double Aggregate<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, bool hasstyle, bool hasjob, Expression<Func<TSource, object>> source, CoreRow? target = null, Expression<Func<StockSummary, object>>? aggregate = null)
  349. {
  350. int srcol = columns.IndexOf(source);
  351. if (srcol == -1)
  352. return 0.00;
  353. var total = rows.Aggregate(0d, (value, row) => value + (double)(row.Values[srcol] ?? 0.0d));
  354. // int productcol = columns.IndexOf(x => x.Product.ID);
  355. // int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  356. // int jobcol = hasjob ? columns.IndexOf(x => x.Job.ID) : -1;
  357. // int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  358. //
  359. // var tuples = rows.Select(r => new Tuple<Guid, Guid?, Guid?, String, double>(
  360. // (Guid)(r.Values[productcol] ?? Guid.Empty),
  361. // (hasstyle ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null),
  362. // (hasjob ? (Guid)(r.Values[jobcol] ?? Guid.Empty) : null),
  363. // (String)(r.Values[unitcol] ?? ""),
  364. // (double)(r.Values[aggcol] ?? 0.0d))
  365. // ).ToArray();
  366. //
  367. // var total = tuples.Aggregate(0d, (value, tuple) => value + tuple.Item5);
  368. if(aggregate is not null)
  369. {
  370. target?.Set(aggregate, total);
  371. }
  372. return total;
  373. }
  374. private class StockSummaryInfo
  375. {
  376. public double MinStock { get; set; }
  377. public double GenStock { get; set; }
  378. public double GenPO { get; set; }
  379. public double JobBOM { get; set; }
  380. public double JobStock { get; set; }
  381. public double JobPO { get; set; }
  382. public double Required => Math.Max((MinStock + JobBOM) - (GenStock + GenPO + JobStock + JobPO), 0.0F);
  383. public double Optimised => Math.Max(Math.Max(MinStock,JobBOM) - (GenStock + GenPO + JobStock + JobPO), 0.0F);
  384. }
  385. private Dictionary<Guid, StockSummaryInfo> _summaryinfo = new Dictionary<Guid, StockSummaryInfo>();
  386. protected override void Reload(Filters<ProductInstance> criteria, Columns<ProductInstance> columns, ref SortOrder<ProductInstance>? sort,
  387. Action<CoreTable?, Exception?> action)
  388. {
  389. var query = new MultiQuery();
  390. query.Add<ProductInstance>(
  391. GroupIDs.Length == 0
  392. ? new Filter<ProductInstance>().None()
  393. : new Filter<ProductInstance>(pi=>pi.Product.Group.ID).InList(GroupIDs),
  394. columns,
  395. new SortOrder<ProductInstance>(x=>x.Product.Code)
  396. );
  397. query.Add<StockHolding>(
  398. GroupIDs.Length == 0
  399. ? new Filter<StockHolding>().None()
  400. : new Filter<StockHolding>(x=>x.Product.Group.ID).InList(GroupIDs)
  401. .And(new Filter<StockHolding>(x=>x.Job.ID).InList(JobIDs).Or(x=>x.Job.ID).IsEqualTo(Guid.Empty)),
  402. new Columns<StockHolding>(x=>x.Product.ID)
  403. .Add(x=>x.Job.ID)
  404. .Add(x=>x.Style.ID)
  405. .Add(x=>x.Dimensions.Unit.ID)
  406. .Add(x=>x.Dimensions.UnitSize)
  407. .Add(x=>x.Units),
  408. null
  409. );
  410. query.Add<PurchaseOrderItem>(
  411. GroupIDs.Length == 0
  412. ? new Filter<PurchaseOrderItem>().None()
  413. : new Filter<PurchaseOrderItem>(x=>x.Product.Group.ID).InList(GroupIDs)
  414. .And(x=>x.ReceivedDate).IsEqualTo(DateTime.MinValue)
  415. .And(new Filter<PurchaseOrderItem>(x=>x.Job.ID).InList(JobIDs).Or(x=>x.Job.ID).IsEqualTo(Guid.Empty)),
  416. new Columns<PurchaseOrderItem>(x=>x.Product.ID)
  417. .Add(x=>x.Job.ID)
  418. .Add(x=>x.Style.ID)
  419. .Add(x=>x.Dimensions.Unit.ID)
  420. .Add(x=>x.Dimensions.UnitSize)
  421. .Add(x=>x.Qty),
  422. null
  423. );
  424. query.Add<JobBillOfMaterialsItem>(
  425. GroupIDs.Length == 0
  426. ? new Filter<JobBillOfMaterialsItem>().None()
  427. : new Filter<JobBillOfMaterialsItem>(x=>x.Product.Group.ID).InList(GroupIDs)
  428. .And(new Filter<JobBillOfMaterialsItem>(x=>x.Job.ID).InList(JobIDs).Or(x=>x.Job.ID).IsEqualTo(Guid.Empty)),
  429. new Columns<JobBillOfMaterialsItem>(x=>x.Product.ID)
  430. .Add(x=>x.Job.ID)
  431. .Add(x=>x.Style.ID)
  432. .Add(x=>x.Dimensions.Unit.ID)
  433. .Add(x=>x.Dimensions.UnitSize)
  434. .Add(x=>x.Quantity),
  435. null
  436. );
  437. query.Add<StockMovement>(
  438. GroupIDs.Length == 0
  439. ? new Filter<StockMovement>().None()
  440. : new Filter<StockMovement>(x=>x.Product.Group.ID).InList(GroupIDs)
  441. .And(x=>x.Type).IsEqualTo(StockMovementType.Issue)
  442. .And(new Filter<StockMovement>(x=>x.Job.ID).InList(JobIDs).Or(x=>x.Job.ID).IsEqualTo(Guid.Empty)),
  443. new Columns<StockMovement>(x=>x.Product.ID)
  444. .Add(x=>x.Job.ID)
  445. .Add(x=>x.Style.ID)
  446. .Add(x=>x.Dimensions.Unit.ID)
  447. .Add(x=>x.Dimensions.UnitSize)
  448. .Add(x=>x.Units),
  449. null
  450. );
  451. query.Add<SupplierProduct>(
  452. GroupIDs.Length == 0
  453. ? new Filter<SupplierProduct>().None()
  454. : new Filter<SupplierProduct>(x => x.Product.Group.ID).InList(GroupIDs)
  455. .And(new Filter<SupplierProduct>(x => x.Job.ID).InList(JobIDs).Or(x => x.Job.ID).IsEqualTo(Guid.Empty)),
  456. new Columns<SupplierProduct>(x=>x.Product.ID)
  457. .Add(x=>x.Job.ID)
  458. .Add(x=>x.Style.ID)
  459. .Add(x=>x.Dimensions.Unit.ID)
  460. .Add(x=>x.Dimensions.UnitSize)
  461. .Add(x=>x.SupplierLink.ID)
  462. .Add(x=>x.CostPrice)
  463. .Add(x=>x.Discount)
  464. .Add(x=>x.TaxCode.ID),
  465. null
  466. );
  467. query.Query(q =>
  468. {
  469. _stockHoldings = query.Get<StockHolding>();
  470. _poItems = query.Get<PurchaseOrderItem>();
  471. _jobBOMs = query.Get<JobBillOfMaterialsItem>();
  472. _stockMovements = query.Get<StockMovement>();
  473. _supplierProducts = query.Get<SupplierProduct>();
  474. var products = query.Get<ProductInstance>();
  475. _summaryinfo.Clear();
  476. var _productInstanceCols = new Columns<ProductInstance>(products.Columns);
  477. var _idCol = _productInstanceCols.IndexOf(x => x.ID);
  478. var _productIDCol = _productInstanceCols.IndexOf(x => x.Product.ID);
  479. var _styleIDCol = _productInstanceCols.IndexOf(x => x.Style.ID);
  480. var _unitSizeCol = _productInstanceCols.IndexOf(x => x.Dimensions.UnitSize);
  481. var _stockHoldingCols = new Columns<StockHolding>(_stockHoldings.Columns);
  482. var _poItemCols = new Columns<PurchaseOrderItem>(_poItems.Columns);
  483. var _jobBOMColumns = new Columns<JobBillOfMaterialsItem>(_jobBOMs.Columns);
  484. var _stockMovementCols = new Columns<StockMovement>(_stockMovements.Columns);
  485. foreach (var row in products.Rows)
  486. {
  487. var _id = row.Values[_idCol] as Guid? ?? Guid.Empty;
  488. var _productid = row.Values[_productIDCol] as Guid? ?? Guid.Empty;
  489. var _styleid = row.Values[_styleIDCol] as Guid? ?? Guid.Empty;
  490. var _unitsize = row.Values[_unitSizeCol] as string ?? "";
  491. var info = new StockSummaryInfo();
  492. info.MinStock = row.Get<ProductInstance, double>(x => x.MinimumStockLevel);
  493. var genstockrows = GetRows<StockHolding>(_stockHoldings.Rows, _stockHoldingCols, _productid, _styleid, _unitsize, new Guid[] { Guid.Empty });
  494. info.GenStock = Aggregate(genstockrows, _stockHoldingCols, true, true, x=>x.Units);
  495. var genporows = GetRows(_poItems.Rows, _poItemCols, _productid, _styleid, _unitsize, new Guid[] { Guid.Empty });
  496. info.GenPO = Aggregate(genporows, _poItemCols, true, true, x=>x.Qty);
  497. var bomrows = GetRows(_jobBOMs.Rows, _jobBOMColumns, _productid, _styleid, _unitsize, JobIDs);
  498. var bom = Aggregate(bomrows, _jobBOMColumns, true, true, x=>x.Quantity);
  499. var mvmtrows = GetRows(_stockMovements.Rows, _stockMovementCols, _productid, _styleid, _unitsize, JobIDs);
  500. var mvmts = Aggregate(mvmtrows, _jobBOMColumns, true, true, x=>x.Quantity);
  501. info.JobBOM = bom - mvmts;
  502. var jobstockrows = GetRows<StockHolding>(_stockHoldings.Rows, _stockHoldingCols, _productid, _styleid, _unitsize, JobIDs);
  503. info.JobStock = Aggregate(jobstockrows, _stockHoldingCols, true, true, x=>x.Units);
  504. var jobporows = GetRows(_poItems.Rows, _poItemCols, _productid, _styleid, _unitsize, JobIDs);
  505. info.JobPO = Aggregate(jobporows, _poItemCols, true, true, x=>x.Qty);
  506. _summaryinfo[_id] = info;
  507. }
  508. // Process the tables here
  509. action.Invoke(query.Get<ProductInstance>(), null);
  510. });
  511. }
  512. protected override bool FilterRecord(CoreRow row)
  513. {
  514. bool result = base.FilterRecord(row);
  515. if (_summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  516. {
  517. if (RequiredOnly)
  518. {
  519. result = result && Optimise
  520. ? !info.Optimised.IsEffectivelyEqual(0)
  521. : !info.Required.IsEffectivelyEqual(0);
  522. }
  523. if (!AllStock)
  524. result = result && _uicomponent?.CheckSuppliers(row) == true;
  525. }
  526. return result;
  527. }
  528. private object GetMinimumStockLevel(CoreRow? row) => row?.Get<ProductInstance, double>(x => x.MinimumStockLevel) ?? 0.0F;
  529. private object GetGeneralStockLevel(CoreRow? row)
  530. {
  531. if (row != null && _summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  532. return info.GenStock;
  533. return 0.0F;
  534. }
  535. private object GetGeneralPurchaseOrder(CoreRow? row)
  536. {
  537. if (row != null && _summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  538. return info.GenPO;
  539. return 0.0F;
  540. }
  541. private object GetBOMBalance(CoreRow? row)
  542. {
  543. if (row != null && _summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  544. return info.JobBOM;
  545. return 0.0F;
  546. }
  547. private object GetReservedStock(CoreRow? row)
  548. {
  549. if (row != null && _summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  550. return info.JobStock;
  551. return 0.0F;
  552. }
  553. private object GetReservedPurchaseOrder(CoreRow? row)
  554. {
  555. if (row != null && _summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  556. return info.JobPO;
  557. return 0.0F;
  558. }
  559. private object GetBalanceRequired(CoreRow? row)
  560. {
  561. if (row != null &&
  562. _summaryinfo.TryGetValue(row.Get<ProductInstance, Guid>(x => x.ID), out StockSummaryInfo? info))
  563. return Optimise
  564. ? info.Optimised.IsEffectivelyEqual(0.0F) ? "" : $"{info.Optimised:F2}"
  565. : info.Required.IsEffectivelyEqual(0.0F)
  566. ? ""
  567. : $"{info.Required:F2}";
  568. return "";
  569. }
  570. private bool OrderStock_Click(Button button, CoreRow[] rows)
  571. {
  572. if(rows.Length == 0)
  573. {
  574. return false;
  575. }
  576. var _instances = rows.ToObjects<ProductInstance>().ToArray();
  577. var order = new PurchaseOrder();
  578. order.Description = "Purchase Order created from Stock Forecast Screen";
  579. order.RaisedBy.ID = App.EmployeeID;
  580. order.IssuedBy.ID = App.EmployeeID;
  581. order.IssuedDate = DateTime.Today;
  582. var orderItems = new List<PurchaseOrderItem>();
  583. foreach(var stockSummary in _instances)
  584. {
  585. var orderItem = new PurchaseOrderItem();
  586. orderItem.Product.ID = stockSummary.Product.ID;
  587. orderItem.Style.ID = stockSummary.Style.ID;
  588. // Need to Breakout BOMs into individual Lines
  589. //orderItem.Job.ID = stockSummary.Job.ID;
  590. orderItem.Dimensions.CopyFrom(stockSummary.Dimensions);
  591. // Need to calculate Balance based on Supplier.OrderQty
  592. //orderItem.Qty = Math.Max(-stockSummary.BalanceAvailable, 0);
  593. orderItems.Add(orderItem);
  594. }
  595. LookupFactory.DoLookups<PurchaseOrderItem, Product, ProductLink>(
  596. orderItems.Select(x => new Tuple<PurchaseOrderItem, Guid>(x, x.Product.ID)),
  597. x => x.Product);
  598. LookupFactory.DoLookups<PurchaseOrderItem, ProductStyle, ProductStyleLink>(
  599. orderItems.Select(x => new Tuple<PurchaseOrderItem, Guid>(x, x.Style.ID)),
  600. x => x.Style);
  601. LookupFactory.DoLookups<PurchaseOrderItem, Job, JobLink>(
  602. orderItems.Select(x => new Tuple<PurchaseOrderItem, Guid>(x, x.Job.ID)),
  603. x => x.Job);
  604. if (DynamicGridUtils.EditEntity(order, t =>
  605. {
  606. if (t == typeof(PurchaseOrderItem))
  607. {
  608. var table = new CoreTable();
  609. table.LoadColumns(typeof(PurchaseOrderItem));
  610. table.LoadRows(orderItems);
  611. return table;
  612. }
  613. return null;
  614. }, preloadPages: true))
  615. {
  616. MessageWindow.ShowMessage("Purchase order created.", "Success.");
  617. }
  618. return false;
  619. }
  620. #region IDataModelSource
  621. public event DataModelUpdateEvent? OnUpdateDataModel;
  622. public string SectionName => "Stock Summary";
  623. public bool Optimise { get; set; }
  624. public bool AllStock { get; set; }
  625. public bool RequiredOnly { get; set; }
  626. public DataModel DataModel(Selection selection)
  627. {
  628. return new AutoDataModel<StockSummary>(null);
  629. }
  630. #endregion
  631. }