StockSummaryGrid.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  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.Media;
  7. using Comal.Classes;
  8. using InABox.Clients;
  9. using InABox.Core;
  10. using InABox.DynamicGrid;
  11. using PRSDesktop.Utils;
  12. using Syncfusion.Data.Extensions;
  13. using Xceed.Wpf.Toolkit;
  14. namespace PRSDesktop
  15. {
  16. public class StockSummaryGrid : DynamicDataGrid<StockSummary>, IDataModelSource
  17. {
  18. public Guid[] GroupIDs { get; set; }
  19. public Guid[] JobIDs { get; set; }
  20. public StockSummaryGrid() : base()
  21. {
  22. ColumnsTag = "StockSummaryGrid";
  23. GroupIDs = new Guid[] { };
  24. JobIDs = new Guid[] { };
  25. HiddenColumns.Add(x => x.Product.ID);
  26. HiddenColumns.Add(x => x.Style.ID);
  27. HiddenColumns.Add(x => x.Job.ID);
  28. HiddenColumns.Add(x => x.Dimensions.UnitSize);
  29. HiddenColumns.Add(x => x.Product.MinimumStockLevel);
  30. HiddenColumns.Add(x => x.Issued);
  31. HiddenColumns.Add(x => x.TotalRequired);
  32. HiddenColumns.Add(x => x.NettRequired);
  33. HiddenColumns.Add(x => x.AllStock);
  34. HiddenColumns.Add(x => x.OnOrder);
  35. HiddenColumns.Add(x => x.TotalStock);
  36. HiddenColumns.Add(x => x.BalanceAvailable);
  37. HiddenColumns.Add(x => x.Product.Image.ID);
  38. HiddenColumns.Add(x => x.Product.Image.FileName);
  39. ActionColumns.Add(new DynamicImagePreviewColumn<StockSummary>(x => x.Product.Image)
  40. { Position = DynamicActionColumnPosition.Start });
  41. OnCellDoubleClick += StockSummaryGrid_OnCellDoubleClick;
  42. }
  43. protected override void DoReconfigure(FluentList<DynamicGridOption> options)
  44. {
  45. base.DoReconfigure(options);
  46. options.AddRange(
  47. DynamicGridOption.RecordCount,
  48. DynamicGridOption.SelectColumns,
  49. DynamicGridOption.FilterRows,
  50. DynamicGridOption.ExportData,
  51. DynamicGridOption.MultiSelect
  52. );
  53. }
  54. protected override void GenerateColumns(DynamicGridColumns columns)
  55. {
  56. columns.Add<StockSummary, string>(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
  57. columns.Add<StockSummary, string>(x => x.Style.Code, 120, "Style Code", "", Alignment.MiddleCenter);
  58. columns.Add<StockSummary, string>(x => x.Dimensions.UnitSize, 120, "Unit Size", "", Alignment.MiddleCenter);
  59. columns.Add<StockSummary, int>(x => x.Product.MinimumStockLevel, 120, "Minimum Stock Level", "", Alignment.MiddleCenter);
  60. columns.Add<StockSummary, double>(x => x.BillOfMaterials, 120, "BOM", "", Alignment.MiddleCenter);
  61. columns.Add<StockSummary, double>(x => x.Issued, 120, "Issued", "", Alignment.MiddleCenter);
  62. columns.Add<StockSummary, double>(x => x.AllStock, 120, "Stock", "", Alignment.MiddleCenter);
  63. columns.Add<StockSummary, double>(x => x.OnOrder, 120, "On Order", "", Alignment.MiddleCenter);
  64. columns.Add<StockSummary, double>(x => x.BalanceAvailable, 120, "Balance Available", "", Alignment.MiddleCenter);
  65. }
  66. private void ShowDetailGrid<TEntity>(
  67. String columnname,
  68. Expression<Func<TEntity,object?>> productcol,
  69. Guid productid,
  70. Expression<Func<TEntity,object?>> stylecol,
  71. Guid? styleid,
  72. Expression<Func<TEntity,object?>> unitcol,
  73. String unitsize,
  74. Expression<Func<TEntity,object?>>? jobcol,
  75. Filter<TEntity>? extrafilter,
  76. Func<CoreRow,bool>? rowfilter
  77. )
  78. {
  79. var grid = (Activator.CreateInstance(typeof(DynamicDataGrid<>).MakeGenericType(typeof(TEntity))) as IDynamicDataGrid);
  80. if (grid == null)
  81. {
  82. MessageBox.Show($"Cannot create Grid for [{typeof(TEntity).Name}]");
  83. return;
  84. }
  85. grid.ColumnsTag = $"{ColumnsTag}.{columnname}";
  86. grid.Reconfigure(options => { options.BeginUpdate().Clear().AddRange(DynamicGridOption.FilterRows, DynamicGridOption.SelectColumns).EndUpdate(); });
  87. grid.OnDefineFilter += t =>
  88. {
  89. var filter = new Filter<TEntity>(productcol).IsEqualTo(productid)
  90. .And(unitcol).IsEqualTo(unitsize);
  91. if (styleid.HasValue)
  92. filter = filter.And(stylecol).IsEqualTo(styleid);
  93. if (jobcol != null)
  94. filter = filter.And(jobcol).InList(JobIDs);
  95. if (extrafilter != null)
  96. filter = filter.And(extrafilter);
  97. return filter;
  98. };
  99. grid.OnFilterRecord += row => rowfilter?.Invoke(row) ?? true;
  100. var window = DynamicGridUtils.CreateGridWindow($"Viewing {CoreUtils.Neatify(columnname)} Calculation", (grid as BaseDynamicGrid)!);
  101. window.ShowDialog();
  102. }
  103. private void StockSummaryGrid_OnCellDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  104. {
  105. Guid productid = args.Row.Get<StockSummary, Guid>(c => c.Product.ID);
  106. Guid? styleid = HasStyle() ? args.Row.Get<StockSummary, Guid>(c => c.Style.ID) : null;
  107. String unitsize = args.Row.Get<StockSummary, String>(c => c.Dimensions.UnitSize);
  108. if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".")))
  109. {
  110. ShowDetailGrid<JobBillOfMaterialsItem>(
  111. args.Column.ColumnName,
  112. x => x.Product.ID,
  113. productid,
  114. x => x.Style.ID,
  115. styleid,
  116. x=>x.Dimensions.UnitSize,
  117. unitsize,
  118. x => x.Job.ID,
  119. new Filter<JobBillOfMaterialsItem>(x=>x.BillOfMaterials.Approved).IsNotEqualTo(DateTime.MinValue),
  120. null
  121. );
  122. }
  123. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".")))
  124. {
  125. ShowDetailGrid<StockMovement>(
  126. args.Column.ColumnName,
  127. x => x.Product.ID,
  128. productid,
  129. x => x.Style.ID,
  130. styleid,
  131. x=>x.Dimensions.UnitSize,
  132. unitsize,
  133. x => x.Job.ID,
  134. new Filter<StockMovement>(x=>x.IsTransfer).IsEqualTo(false).And(x=>x.Issued).IsNotEqualTo(0.0F),
  135. null
  136. );
  137. }
  138. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalRequired, ".")))
  139. {
  140. ShowDetailGrid<StockSummary>(
  141. args.Column.ColumnName,
  142. x => x.Product.ID,
  143. productid,
  144. x => x.Style.ID,
  145. styleid,
  146. x=>x.Dimensions.UnitSize,
  147. unitsize,
  148. x => x.Job.ID,
  149. null,
  150. null
  151. );
  152. }
  153. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".")))
  154. {
  155. ShowDetailGrid<StockSummary>(
  156. args.Column.ColumnName,
  157. x => x.Product.ID,
  158. productid,
  159. x => x.Style.ID,
  160. styleid,
  161. x=>x.Dimensions.UnitSize,
  162. unitsize,
  163. x => x.Job.ID,
  164. null,
  165. null
  166. );
  167. }
  168. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".")))
  169. {
  170. ShowDetailGrid<StockHolding>(
  171. args.Column.ColumnName,
  172. x => x.Product.ID,
  173. productid,
  174. x => x.Style.ID,
  175. styleid,
  176. x=>x.Dimensions.UnitSize,
  177. unitsize,
  178. null,
  179. null,
  180. (row) => row.Get<StockHolding,double>(x=>x.Units) != 0.0F
  181. );
  182. }
  183. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".")))
  184. {
  185. ShowDetailGrid<PurchaseOrderItem>(
  186. args.Column.ColumnName,
  187. x => x.Product.ID,
  188. productid,
  189. x => x.Style.ID,
  190. styleid,
  191. x=>x.Dimensions.UnitSize,
  192. unitsize,
  193. null,
  194. null,
  195. null
  196. );
  197. }
  198. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.TotalStock, ".")))
  199. {
  200. ShowDetailGrid<StockSummary>(
  201. args.Column.ColumnName,
  202. x => x.Product.ID,
  203. productid,
  204. x => x.Style.ID,
  205. styleid,
  206. x=>x.Dimensions.UnitSize,
  207. unitsize,
  208. x => x.Job.ID,
  209. null,
  210. null
  211. );
  212. }
  213. else if (String.Equals(args.Column.ColumnName, CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".")))
  214. {
  215. ShowDetailGrid<StockSummary>(
  216. args.Column.ColumnName,
  217. x => x.Product.ID,
  218. productid,
  219. x => x.Style.ID,
  220. styleid,
  221. x=>x.Dimensions.UnitSize,
  222. unitsize,
  223. x => x.Job.ID,
  224. null,
  225. null
  226. );
  227. }
  228. }
  229. private bool HasStyle()
  230. {
  231. return DataColumns().ColumnNames().Any(x => x.StartsWith("Style.") && !x.Equals("Style.ID"));
  232. }
  233. private Filters<StockSummary> GetFilters()
  234. {
  235. var filters = new Filters<StockSummary>();
  236. filters.Add(FilterComponent.GetFilter());
  237. Filter<StockSummary>? _groupfilter = !GroupIDs.Any()
  238. ? new Filter<StockSummary>().None()
  239. : !GroupIDs.Contains(CoreUtils.FullGuid)
  240. ? new Filter<StockSummary>(x => x.Product.Group.ID).InList(GroupIDs)
  241. : null;
  242. filters.Add(_groupfilter);
  243. var jobFilter = !JobIDs.Any()
  244. ? new Filter<StockSummary>().None()
  245. : new Filter<StockSummary>(x => x.Job.ID).InList(JobIDs);
  246. filters.Add(jobFilter);
  247. //Filter<StockSummary> _productfilter = new Filter<StockSummary>(x => x.Product.ID).IsNotEqualTo(Guid.Empty);
  248. //filters.Add(_productfilter);
  249. return filters;
  250. }
  251. private Tuple<Guid,Guid?,String>[] GetKeys(IEnumerable<CoreRow> rows, Columns<StockSummary> columns, bool hasstyle)
  252. {
  253. int productcol = columns.IndexOf(x => x.Product.ID);
  254. int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  255. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  256. var result = rows.Select(r => new Tuple<Guid, Guid?, String>(
  257. (Guid)(r.Values[productcol] ?? Guid.Empty),
  258. (stylecol != -1) ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null,
  259. (String)(r.Values[unitcol] ?? ""))
  260. ).Distinct().ToArray();
  261. return result;
  262. }
  263. private CoreRow[] GetRows<TSource>(IEnumerable<CoreRow> rows, Columns<TSource> columns, Guid productid, Guid? styleid, String unitsize) where TSource : IJobMaterial
  264. {
  265. int productcol = columns.IndexOf(x => x.Product.ID);
  266. int stylecol = styleid.HasValue ? columns.IndexOf(x => x.Style.ID) : -1;
  267. int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  268. var subset = rows
  269. .Where(r=>
  270. Guid.Equals(r.Values[productcol], productid)
  271. && (!styleid.HasValue || Guid.Equals(r.Values[stylecol], styleid))
  272. && String.Equals(r.Values[unitcol], unitsize)
  273. );
  274. return subset.ToArray();
  275. }
  276. 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 )
  277. {
  278. int srcol = columns.IndexOf(source);
  279. if (srcol == -1)
  280. return 0.00;
  281. var total = rows.Aggregate(0d, (value, row) => value + (double)(row.Values[srcol] ?? 0.0d));
  282. // int productcol = columns.IndexOf(x => x.Product.ID);
  283. // int stylecol = hasstyle ? columns.IndexOf(x => x.Style.ID) : -1;
  284. // int jobcol = hasjob ? columns.IndexOf(x => x.Job.ID) : -1;
  285. // int unitcol = columns.IndexOf(x => x.Dimensions.UnitSize);
  286. //
  287. // var tuples = rows.Select(r => new Tuple<Guid, Guid?, Guid?, String, double>(
  288. // (Guid)(r.Values[productcol] ?? Guid.Empty),
  289. // (hasstyle ? (Guid)(r.Values[stylecol] ?? Guid.Empty) : null),
  290. // (hasjob ? (Guid)(r.Values[jobcol] ?? Guid.Empty) : null),
  291. // (String)(r.Values[unitcol] ?? ""),
  292. // (double)(r.Values[aggcol] ?? 0.0d))
  293. // ).ToArray();
  294. //
  295. // var total = tuples.Aggregate(0d, (value, tuple) => value + tuple.Item5);
  296. target.Set(aggregate, total);
  297. return total;
  298. }
  299. protected override void Reload(Filters<StockSummary> criteria, Columns<StockSummary> columns, ref SortOrder<StockSummary>? sort,
  300. Action<CoreTable?, Exception?> action)
  301. {
  302. var filter = GetFilters().Combine();
  303. new Client<StockSummary>().Query(filter,columns,sort, (o,e) =>
  304. {
  305. var pids = o.ExtractValues<StockSummary, Guid>(x => x.Product.ID).ToArray();
  306. MultiQuery query = new MultiQuery();
  307. query.Add<StockHolding>(
  308. new Filter<StockHolding>(x => x.Product.ID).InList(pids),
  309. new Columns<StockHolding>(x => x.Product.ID)
  310. .Add(x => x.Style.ID)
  311. .Add(x => x.Dimensions.UnitSize)
  312. .Add(x => x.Units)
  313. );
  314. query.Add<PurchaseOrderItem>(
  315. new Filter<PurchaseOrderItem>(x => x.ReceivedDate).IsEqualTo(DateTime.MinValue)
  316. .And(x => x.Product.ID).InList(pids),
  317. new Columns<PurchaseOrderItem>(x => x.Product.ID)
  318. .Add(x => x.Style.ID)
  319. .Add(x => x.Dimensions.UnitSize)
  320. .Add(x => x.Qty)
  321. );
  322. query.Query();
  323. var holdings = query.Get<StockHolding>();
  324. Columns<StockHolding> holdingcolumns = new Columns<StockHolding>(holdings.Columns.Select(x => x.ColumnName));
  325. var orders = query.Get<PurchaseOrderItem>();
  326. Columns<PurchaseOrderItem> ordercolumns = new Columns<PurchaseOrderItem>(orders.Columns.Select(x => x.ColumnName));
  327. CoreTable table = new CoreTable();
  328. table.LoadColumns(columns);
  329. if (o != null)
  330. {
  331. bool bHasStyle = HasStyle();
  332. var keys = GetKeys(o.Rows, columns, bHasStyle);
  333. foreach (var key in keys)
  334. {
  335. var rows = GetRows(o.Rows, columns, key.Item1, key.Item2, key.Item3);
  336. if (rows.Any())
  337. {
  338. CoreRow newrow = table.NewRow();
  339. newrow.LoadValues(rows.First().Values);
  340. Aggregate(rows, columns, bHasStyle, true, x => x.BillOfMaterials, newrow, x => x.BillOfMaterials);
  341. Aggregate(rows, columns, bHasStyle, true, x => x.Issued, newrow, x => x.Issued);
  342. Aggregate(rows, columns, bHasStyle, true, x => x.TotalRequired, newrow, x => x.TotalRequired);
  343. var nettrequired = Aggregate(rows, columns, bHasStyle, true, x => x.NettRequired, newrow, x => x.NettRequired);
  344. var holdingrows = GetRows(holdings.Rows, holdingcolumns, key.Item1, key.Item2, key.Item3);
  345. var allstock = Aggregate(holdingrows, holdingcolumns, bHasStyle, false, x => x.Units, newrow, x => x.AllStock);
  346. var orderrows = GetRows(orders.Rows, ordercolumns, key.Item1, key.Item2, key.Item3);
  347. var onorder = Aggregate(orderrows, ordercolumns, bHasStyle, false, x => x.Qty, newrow, x => x.OnOrder);
  348. newrow.Set<StockSummary, double>(x => x.TotalStock, allstock + onorder);
  349. newrow.Set<StockSummary, double>(x => x.BalanceAvailable, allstock + onorder - (newrow.Get<StockSummary,double>(c=>c.Product.MinimumStockLevel) + nettrequired));
  350. table.Rows.Add(newrow);
  351. }
  352. }
  353. }
  354. action?.Invoke(table, e);
  355. });
  356. }
  357. protected override bool FilterRecord(CoreRow row)
  358. {
  359. var result = base.FilterRecord(row);
  360. if (result)
  361. result = (result && (
  362. row.Get<StockSummary, double>(x => x.BillOfMaterials) != 0.0F
  363. || row.Get<StockSummary, double>(x => x.Product.MinimumStockLevel) != 0.0F
  364. || row.Get<StockSummary, double>(x => x.Issued) != 0.0F
  365. || row.Get<StockSummary, double>(x => x.AllStock) != 0.0F
  366. || row.Get<StockSummary, double>(x => x.OnOrder) != 0.0F
  367. )
  368. );
  369. return result;
  370. }
  371. // private String _minstock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Product.MinimumStockLevel, ".");
  372. // private String _bom = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BillOfMaterials, ".");
  373. // private String _issued = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.Issued, ".");
  374. // private String _nettrequired = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.NettRequired, ".");
  375. // private String _stock = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.AllStock, ".");
  376. // private String _ordered = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.OnOrder, ".");
  377. private String _balance = CoreUtils.GetFullPropertyName<StockSummary, double>(x => x.BalanceAvailable, ".");
  378. protected override Brush? GetCellBackground(CoreRow row, string columnname)
  379. {
  380. if (String.Equals(columnname, _balance))
  381. {
  382. return row.Get<StockSummary, double>(x => x.BalanceAvailable) < 0.0F
  383. ? new SolidColorBrush(Colors.LightSalmon) { Opacity = 0.5 }
  384. : null;
  385. }
  386. return null;
  387. }
  388. #region IDataModelSource
  389. public event DataModelUpdateEvent? OnUpdateDataModel;
  390. public string SectionName => "Stock Summary";
  391. public DataModel DataModel(Selection selection)
  392. {
  393. return new AutoDataModel<StockSummary>(null);
  394. }
  395. #endregion
  396. }
  397. }