StockHoldingGrid.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using com.sun.tools.@internal.ws.processor.util;
  8. using Comal.Classes;
  9. using InABox.Clients;
  10. using InABox.Core;
  11. using InABox.DynamicGrid;
  12. using InABox.Wpf;
  13. using InABox.WPF;
  14. using PRSDesktop.Panels.Products.Locations;
  15. using Syncfusion.Windows.Controls.RichTextBoxAdv;
  16. using Exception = System.Exception;
  17. namespace PRSDesktop;
  18. public class StockHoldingGrid : DynamicDataGrid<StockHolding>
  19. {
  20. private enum MovementAction
  21. {
  22. Receive,
  23. Issue,
  24. Transfer
  25. }
  26. private MovementAction _action;
  27. private StockHolding? _holding;
  28. private Button IssueButton;
  29. private Button ReceiveButton;
  30. private DynamicDataGrid<StockMovement> smg;
  31. //Button ReserveButton = null;
  32. private Button TransferButton;
  33. private Button RecalculateButton;
  34. private Button AdjustValueButton;
  35. private Button RelocateButton;
  36. public StockHoldingGrid() : base()
  37. {
  38. ColumnsTag = "StockHolding";
  39. }
  40. protected override void Init()
  41. {
  42. base.Init();
  43. ReceiveButton = AddButton("Receive", PRSDesktop.Resources.add.AsBitmapImage(), ReceiveStock);
  44. ReceiveButton.IsEnabled = false;
  45. IssueButton = AddButton("Issue", PRSDesktop.Resources.delete.AsBitmapImage(), IssueStock);
  46. IssueButton.IsEnabled = false;
  47. //ReserveButton = AddButton("Reserve", PRSDesktop.Resources.project.AsBitmapImage(), ReserveStock);
  48. //ReserveButton.Margin = new Thickness(20, ReserveButton.Margin.Top, ReserveButton.Margin.Right, ReserveButton.Margin.Bottom);
  49. //ReserveButton.IsEnabled = false;
  50. TransferButton = AddButton("Transfer", PRSDesktop.Resources.split.AsBitmapImage(), TransferStock);
  51. TransferButton.Margin = new Thickness(20, TransferButton.Margin.Top, TransferButton.Margin.Right, TransferButton.Margin.Bottom);
  52. TransferButton.IsEnabled = false;
  53. RelocateButton = AddButton("Relocate", PRSDesktop.Resources.box.AsBitmapImage(), RelocateStock);
  54. RelocateButton.IsEnabled = false;
  55. AdjustValueButton = AddButton("Adjust Value", PRSDesktop.Resources.receipt.AsBitmapImage(), AdjustValues,
  56. DynamicGridButtonPosition.Right);
  57. AdjustValueButton.Margin = new Thickness(AdjustValueButton.Margin.Left, AdjustValueButton.Margin.Top, 10, AdjustValueButton.Margin.Bottom);
  58. RecalculateButton = AddButton("Recalculate", PRSDesktop.Resources.service.AsBitmapImage(), RecalculateHoldings,
  59. DynamicGridButtonPosition.Right);
  60. HiddenColumns.Add(x => x.Product.ID);
  61. HiddenColumns.Add(x => x.Job.ID);
  62. HiddenColumns.Add(x => x.Job.JobNumber);
  63. HiddenColumns.Add(x => x.Job.Name);
  64. HiddenColumns.Add(x => x.Location.ID);
  65. HiddenColumns.Add(x => x.Location.Code);
  66. HiddenColumns.Add(x => x.Location.Description);
  67. HiddenColumns.Add(x => x.Style.ID);
  68. HiddenColumns.Add(x => x.Style.Code);
  69. HiddenColumns.Add(x => x.Qty);
  70. HiddenColumns.Add(x => x.Units);
  71. HiddenColumns.Add(x => x.Available);
  72. HiddenColumns.Add(x => x.AverageValue);
  73. HiddenColumns.Add(x => x.Dimensions.Unit.ID);
  74. HiddenColumns.Add(x => x.Dimensions.Unit.Description);
  75. HiddenColumns.Add(x => x.Dimensions.Unit.HasHeight);
  76. HiddenColumns.Add(x => x.Dimensions.Unit.HasLength);
  77. HiddenColumns.Add(x => x.Dimensions.Unit.HasWidth);
  78. HiddenColumns.Add(x => x.Dimensions.Unit.HasHeight);
  79. HiddenColumns.Add(x => x.Dimensions.Unit.HasQuantity);
  80. HiddenColumns.Add(x => x.Dimensions.Unit.Format);
  81. HiddenColumns.Add(x => x.Dimensions.Unit.Formula);
  82. HiddenColumns.Add(x => x.Dimensions.Length);
  83. HiddenColumns.Add(x => x.Dimensions.Width);
  84. HiddenColumns.Add(x => x.Dimensions.Height);
  85. HiddenColumns.Add(x => x.Dimensions.Quantity);
  86. HiddenColumns.Add(x => x.Dimensions.Value);
  87. HiddenColumns.Add(x => x.Dimensions.UnitSize);
  88. ActionColumns.Add(new DynamicMenuColumn(BuildMenu) { Position = DynamicActionColumnPosition.End });
  89. }
  90. private bool AdjustValues(Button arg1, CoreRow[] rows)
  91. {
  92. if (rows?.Any() != true)
  93. return false;
  94. double _newvalue = 0.0;
  95. if (DoubleEdit.Execute("New Average Value", 0, double.MaxValue, ref _newvalue))
  96. {
  97. Progress.ShowModal("Creating Batch", progress =>
  98. {
  99. StockMovementBatch _batch = new StockMovementBatch()
  100. {
  101. Type = StockMovementBatchType.Transfer,
  102. Employee = new EmployeeLink() { ID = App.EmployeeID },
  103. Notes = "Stock Value Adjustment"
  104. };
  105. Client.Save(_batch,"Stock value adjusted from holding grid");
  106. progress.Report("Creating Movements");
  107. List<StockMovement> _updates = new List<StockMovement>();
  108. foreach (var _row in rows)
  109. {
  110. var _holding = _row.ToObject<StockHolding>();
  111. _holding.AverageValue = _newvalue;
  112. _holding.Value = _newvalue * _holding.Units;
  113. Client.Save(_holding,"Stock value adjusted from holding grid");
  114. _updates.AddRange(_holding.AdjustValue(_newvalue, _batch));
  115. }
  116. progress.Report("Saving Movements");
  117. Client.Save(_updates,"Stock value adjusted from holding grid");
  118. });
  119. MessageBox.Show("All Done");
  120. return true;
  121. }
  122. return false;
  123. }
  124. private bool RecalculateHoldings(Button arg1, CoreRow[] arg2)
  125. {
  126. Dictionary<String, int> messages = new();
  127. void AddMessage(String type)
  128. {
  129. messages.TryGetValue(type, out int count);
  130. messages[type] = ++count;
  131. }
  132. Progress.ShowModal("Recalculating", progress =>
  133. {
  134. progress.Report("Loading Data");
  135. MultiQuery query = new MultiQuery();
  136. query.Add(
  137. new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(Location.ID),
  138. Columns.Required<StockHolding>().Add(x => x.ID)
  139. .Add(x => x.Product.ID)
  140. .Add(x => x.Job.ID)
  141. .Add(x => x.Style.ID)
  142. .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
  143. .Add(x => x.Units)
  144. .Add(x => x.AverageValue)
  145. .Add(x => x.Available)
  146. .Add(x => x.Qty)
  147. .Add(x => x.Weight)
  148. .Add(x => x.Value)
  149. );
  150. query.Add(
  151. new Filter<StockMovement>(x => x.Location.ID).IsEqualTo(Location.ID),
  152. Columns.None<StockMovement>().Add(x => x.ID)
  153. .Add(x => x.Product.ID)
  154. .Add(x => x.Job.ID)
  155. .Add(x => x.Style.ID)
  156. .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
  157. .Add(x => x.Units)
  158. .Add(x => x.Cost)
  159. .Add(x => x.JobRequisitionItem.ID)
  160. );
  161. query.Query();
  162. var holdings = query.Get<StockHolding>().ToObjects<StockHolding>().ToList();
  163. var toDelete = new List<StockHolding>();
  164. var movements = query.Get<StockMovement>().ToObjects<StockMovement>().ToList();
  165. progress.Report("Processing");
  166. var updates = new List<StockHolding>();
  167. while (movements.Any())
  168. {
  169. var first = movements.First();
  170. var selected = movements.Where(x => x.IsEqualTo(first)).ToList();
  171. var holding = holdings.FirstOrDefault(x => x.IsEqualTo(first));
  172. if (holding == null)
  173. {
  174. holding = new StockHolding();
  175. holding.Location.ID = Location.ID;
  176. holding.Product.ID = first.Product.ID;
  177. holding.Style.ID = first.Style.ID;
  178. holding.Job.ID = first.Job.ID;
  179. holding.Dimensions.CopyFrom(first.Dimensions);
  180. }
  181. holding.Recalculate(selected);
  182. // Removing from the list so that it is not deleted.
  183. if (holdings.Contains(holding))
  184. holdings.Remove(holding);
  185. if (holding.Units.IsEffectivelyEqual(0.0) && holding.Available.IsEffectivelyEqual(0.0))
  186. {
  187. if(holding.ID != Guid.Empty)
  188. {
  189. toDelete.Add(holding);
  190. }
  191. }
  192. else if (holding.IsChanged())
  193. {
  194. AddMessage(holding.ID != Guid.Empty ? "updated" : "added");
  195. updates.Add(holding);
  196. }
  197. movements.RemoveAll(x => selected.Any(s => s.ID == x.ID));
  198. }
  199. toDelete.AddRange(holdings);
  200. foreach (var holding in toDelete)
  201. AddMessage("deleted");
  202. if (updates.Any())
  203. {
  204. progress.Report($"Updating {updates.Count} Holdings");
  205. new Client<StockHolding>().Save(updates.Where(x => x.IsChanged()), "Updated by Recalculation");
  206. }
  207. if (toDelete.Any())
  208. {
  209. progress.Report($"Deleting {toDelete.Count} Holdings");
  210. new Client<StockHolding>().Delete(toDelete, "Removed by Recalculation");
  211. }
  212. });
  213. MessageWindow.ShowMessage(
  214. messages.Any()
  215. ? String.Join("\n", messages.Select(x => $"{x.Value} holdings {x.Key}"))
  216. : "Nothing to Update!"
  217. ,"Recalculate");
  218. return true;
  219. }
  220. public override DynamicGridColumns GenerateColumns()
  221. {
  222. var columns = new DynamicGridColumns<StockHolding>();
  223. columns.Add(x => x.Product.Code, 120, "Product Code", "", Alignment.MiddleCenter);
  224. columns.Add(x => x.Product.Name, 0, "Product Name", "", Alignment.MiddleLeft);
  225. columns.Add(x => x.Job.JobNumber, 50, "Job", "", Alignment.MiddleCenter);
  226. columns.Add(x => x.Style.Description, 0, "Style", "", Alignment.MiddleLeft);
  227. columns.Add(x => x.Dimensions.UnitSize, 100, "Size", "", Alignment.MiddleCenter);
  228. columns.Add(x => x.Units, 70, "Units", "", Alignment.MiddleCenter);
  229. columns.Add(x => x.Available, 70, "Available", "", Alignment.MiddleCenter);
  230. return columns;
  231. }
  232. protected override void DoReconfigure(DynamicGridOptions options)
  233. {
  234. base.DoReconfigure(options);
  235. options.AddRows = false;
  236. options.EditRows = Security.CanEdit<StockHolding>();
  237. options.DeleteRows = false;
  238. options.RecordCount = true;
  239. options.SelectColumns = true;
  240. options.FilterRows = true;
  241. }
  242. private void BuildMenu(DynamicMenuColumn column, CoreRow? row)
  243. {
  244. if (row is null) return;
  245. var holding = row.ToObject<StockHolding>();
  246. if (holding.Available.IsEffectivelyEqual(holding.Units))
  247. column.AddItem("(No Requisitions in this Holding", null, null).IsEnabled = false;
  248. else
  249. {
  250. column.AddItem("View Requisition Items", null, ViewRequisitions_Click);
  251. if (Security.IsAllowed<CanEditAllocatedJobRequisitions>())
  252. {
  253. column.GetMenu().AddItem("Release allocated stock", null, holding, ReleaseAllocatedStock_Click);
  254. }
  255. }
  256. column.AddSeparator();
  257. column.AddItem("Relocate Items", null, r =>
  258. {
  259. var requiitems = holding.LoadRequisitionItems(true).ToArray();
  260. RelocateItems(holding, requiitems);
  261. });
  262. if (holding.Dimensions.Unit.HasDimensions() && holding.Available.IsEffectivelyGreaterThan(0.0))
  263. column.AddItem("Convert Dimensions", null, r =>
  264. {
  265. var calculator = new StockTransformWindow(holding);
  266. if (calculator.ShowDialog() == true)
  267. {
  268. var transferout = holding.CreateMovement();
  269. transferout.Date = DateTime.Now;
  270. transferout.Issued = calculator.OldAvailable;
  271. transferout.Transaction = Guid.NewGuid();
  272. transferout.Type = StockMovementType.TransferOut;
  273. var transferin = holding.CreateMovement();
  274. transferin.Date = transferout.Date.AddTicks(1);
  275. transferin.Dimensions.CopyFrom(calculator.Dimensions);
  276. transferin.Received = calculator.NewAvailable;
  277. transferin.Transaction = transferout.Transaction;
  278. transferin.Type = StockMovementType.TransferIn;
  279. Client.Save([transferout,transferin], "Converted Dimensions");
  280. Refresh(false,true);
  281. }
  282. });
  283. }
  284. private void DoTransfer(StockHolding holding, IList<JobRequisitionItem> requiitems, Func<JobRequisitionItem, double?> getQuantity, Action<JobRequisitionItem, StockMovement, StockMovement> modify)
  285. {
  286. var updates = new List<StockMovement>();
  287. DoTransfer(holding, requiitems, getQuantity, modify, updates);
  288. SaveBatch(StockMovementBatchType.Transfer, updates);
  289. DoChanged();
  290. Refresh(false, true);
  291. }
  292. private static void DoTransfer(StockHolding holding, IList<JobRequisitionItem> requiitems, Func<JobRequisitionItem, double?> getQuantity, Action<JobRequisitionItem, StockMovement, StockMovement> modify, List<StockMovement> updates)
  293. {
  294. foreach(var requi in requiitems)
  295. {
  296. var qty = getQuantity(requi);
  297. if (!qty.HasValue || qty.Value == 0) continue;
  298. var mout = holding.CreateMovement();
  299. mout.Cost = holding.AverageValue;
  300. mout.Date = DateTime.Now;
  301. mout.Employee.ID = App.EmployeeID;
  302. var min = mout.CreateMovement();
  303. min.Cost = holding.AverageValue;
  304. min.Employee.ID = App.EmployeeID;
  305. min.Date = mout.Date;
  306. min.Transaction = mout.Transaction;
  307. if(qty.Value > 0)
  308. {
  309. mout.Issued = qty.Value;
  310. mout.Type = StockMovementType.TransferOut;
  311. min.Received = qty.Value;
  312. min.Type = StockMovementType.TransferIn;
  313. }
  314. else
  315. {
  316. min.Issued = -qty.Value;
  317. min.Type = StockMovementType.TransferOut;
  318. mout.Received = -qty.Value;
  319. mout.Type = StockMovementType.TransferIn;
  320. }
  321. modify(requi, mout, min);
  322. updates.Add(mout);
  323. updates.Add(min);
  324. }
  325. }
  326. private void ReleaseAllocatedStock_Click(StockHolding holding)
  327. {
  328. var requiitems = holding.LoadRequisitionItems(true).Where(x => x.ID != Guid.Empty).ToList();
  329. var win = new StockHoldingRelocationWindow(holding, requiitems, StockHoldingRelocationWindow.RelocationMode.ReleaseAllocations);
  330. if (win.ShowDialog() == true)
  331. {
  332. var quantities = win.GetQuantities();
  333. DoTransfer(holding, requiitems, x => quantities.TryGetValue(x.ID, out var value) ? value : null, (requi, mout, min) =>
  334. {
  335. mout.JobRequisitionItem.ID = requi.ID;
  336. mout.Notes = $"Released from Job Requisition {requi.Requisition.Number}: {requi.Requisition.Description} for Job {requi.Job.JobNumber}";
  337. min.JobRequisitionItem.ID = Guid.Empty;
  338. min.Notes = $"Released from Job Requisition {requi.Requisition.Number}: {requi.Requisition.Description} for Job {requi.Job.JobNumber}";
  339. });
  340. }
  341. }
  342. private void RelocateItems(StockHolding holding, JobRequisitionItem[] requiitems)
  343. {
  344. var win = new StockHoldingRelocationWindow(holding, requiitems, StockHoldingRelocationWindow.RelocationMode.Transfer)
  345. {
  346. OriginalTarget = new StockLocation
  347. {
  348. ID = holding.Location.ID,
  349. Code = holding.Location.Code,
  350. Description = holding.Location.Description,
  351. },
  352. OriginalJob = new Job
  353. {
  354. ID = holding.Job.ID,
  355. JobNumber = holding.Job.JobNumber,
  356. Name = holding.Job.Name
  357. }
  358. };
  359. if (win.ShowDialog() == true)
  360. {
  361. var quantities = win.GetQuantities();
  362. var target = win.GetTargetLocation();
  363. DoTransfer(holding, requiitems, x => quantities.TryGetValue(x.ID, out var qty) ? qty : null, (requi, mout, min) =>
  364. {
  365. mout.JobRequisitionItem.ID = requi.ID;
  366. mout.Notes = $"Moved to {target.Code} by {App.EmployeeName}";
  367. min.Location.Clear();
  368. min.Location.ID = target.ID;
  369. min.Job.CopyFrom(win.Job ?? new());
  370. min.JobRequisitionItem.ID = requi.ID;
  371. min.Notes = $"Moved From {holding.Location.Code} by {App.EmployeeName}";
  372. });
  373. }
  374. }
  375. private void ViewRequisitions_Click(CoreRow? row)
  376. {
  377. if (row is null) return;
  378. var holding = row.ToObject<StockHolding>();
  379. var grid = (DynamicGridUtils.CreateDynamicGrid(typeof(DynamicDataGrid<>), typeof(JobRequisitionItem)) as DynamicDataGrid<JobRequisitionItem>)!;
  380. grid.OnDefineFilter += (type) =>
  381. {
  382. if(type == typeof(JobRequisitionItem))
  383. {
  384. return new Filter<JobRequisitionItem>(x => x.ID)
  385. .InQuery(
  386. StockHolding.GetFilter(holding),
  387. x => x.JobRequisitionItem.ID)
  388. // We don't care about stuff which has nothing in stock, which means it's been either issued or cancelled.
  389. .And(x => x.InStock).IsNotEqualTo(FilterConstant.Zero);
  390. }
  391. else
  392. {
  393. return null;
  394. }
  395. };
  396. DynamicGridUtils.CreateGridWindow("Job Requisition Items for stock holding", grid).ShowDialog();
  397. }
  398. private bool ReceiveStock(Button arg1, CoreRow[] rows)
  399. {
  400. var movement = new StockMovement();
  401. movement.Location.ID = Location.ID;
  402. movement.Location.Code = Location.Code;
  403. movement.Job.ID = Location.Job.ID;
  404. movement.Job.JobNumber = Location.Job.JobNumber;
  405. movement.Job.Name = Location.Job.Name;
  406. movement.Location.Description = Location.Description;
  407. movement.Date = DateTime.Now;
  408. movement.Employee.ID = App.EmployeeID;
  409. movement.Type = StockMovementType.Receive;
  410. movement.CommitChanges();
  411. var holding = rows.FirstOrDefault()?.ToObject<StockHolding>();
  412. var smg = CheckStockMovementGrid(MovementAction.Receive, holding);
  413. var result = smg.EditItems(new[] { movement });
  414. if (result)
  415. {
  416. DoChanged();
  417. SaveBatch(StockMovementBatchType.Receipt, new StockMovement[] { movement});
  418. }
  419. return result;
  420. }
  421. private bool IssueStock(Button arg1, CoreRow[] rows)
  422. {
  423. if (rows?.Length != 1)
  424. {
  425. MessageWindow.ShowMessage("Please select an item to issue", "No selected items");
  426. return false;
  427. }
  428. var holding = rows.First().ToObject<StockHolding>();
  429. var items = holding.LoadRequisitionItems(true).AsArray();
  430. DoIssue(holding, items);
  431. return false;
  432. }
  433. private IEnumerable<StockMovement> CreateIssue(StockHolding holding, IJob? job, double qty, IJobRequisitionItem requi)
  434. {
  435. var issue = CreateMovementFromHolding(holding);
  436. issue.Job.ID = job?.ID ?? Guid.Empty;
  437. issue.Type = StockMovementType.Issue;
  438. issue.JobRequisitionItem.ID = requi.ID;
  439. issue.Issued = qty;
  440. issue.Notes = $"Issued by {App.EmployeeName}";
  441. issue.Date = DateTime.Now;
  442. yield return issue;
  443. if (holding.Job.ID != issue.Job.ID)
  444. {
  445. var xferout = CreateMovementFromHolding(holding);
  446. xferout.Type = StockMovementType.TransferOut;
  447. xferout.JobRequisitionItem.ID = requi.ID;
  448. xferout.Issued = qty;
  449. xferout.Transaction = issue.Transaction;
  450. xferout.System = true;
  451. xferout.Notes = $"Issued by {App.EmployeeName}";
  452. xferout.Date = issue.Date;
  453. yield return xferout;
  454. var xferin = CreateMovementFromHolding(holding);
  455. xferin.Job.ID = issue.Job.ID;
  456. xferin.Type = StockMovementType.TransferIn;
  457. xferin.JobRequisitionItem.ID = requi.ID;
  458. xferin.Received = qty;
  459. xferin.Transaction = issue.Transaction;
  460. xferin.System = true;
  461. xferin.Notes = $"Issued by {App.EmployeeName}";
  462. xferin.Date = issue.Date;
  463. yield return xferin;
  464. }
  465. }
  466. private void DoIssue(StockHolding holding, JobRequisitionItem[] requiitems)
  467. {
  468. var updates = new List<StockMovement>();
  469. var win = new StockHoldingRelocationWindow(holding, requiitems, StockHoldingRelocationWindow.RelocationMode.Issue)
  470. {
  471. OriginalJob = new Job
  472. {
  473. ID = holding.Job.ID,
  474. JobNumber = holding.Job.JobNumber
  475. }
  476. };
  477. if (win.ShowDialog() == true)
  478. {
  479. var quantities = win.GetQuantities();
  480. foreach(var requi in requiitems)
  481. {
  482. if (!quantities.TryGetValue(requi.ID, out var qty) || qty <= 0) continue;
  483. updates.AddRange(CreateIssue(holding, win.Job, qty, requi));
  484. }
  485. SaveBatch(StockMovementBatchType.Issue, updates);
  486. DoChanged();
  487. Refresh(false,true);
  488. }
  489. }
  490. private bool TransferStock(Button arg1, CoreRow[] rows)
  491. {
  492. if (rows?.Length != 1)
  493. return false;
  494. var holding = rows.First().ToObject<StockHolding>();
  495. var items = holding.LoadRequisitionItems(true).AsArray();
  496. RelocateItems(holding, items);
  497. return false;
  498. }
  499. private bool RelocateStock(Button btn, CoreRow[] rows)
  500. {
  501. StockLocation? target = null;
  502. while(true)
  503. {
  504. target = StockHoldingRelocationWindow.LookupLocation();
  505. if(target is null)
  506. {
  507. return false;
  508. }
  509. else if(target.ID == Location.ID)
  510. {
  511. MessageWindow.ShowMessage($"These items are already in {target.Code}; please select a different location.", "Invalid transfer");
  512. }
  513. else
  514. {
  515. break;
  516. }
  517. }
  518. var holdings = rows.ToArray<StockHolding>();
  519. var updates = new List<StockMovement>();
  520. foreach(var holding in holdings)
  521. {
  522. var items = holding.LoadRequisitionItems(true).AsIList();
  523. var rIDs = items.Select(x => x.ID).Where(x => x != Guid.Empty).ToArray();
  524. var quantities = Client.Query(
  525. StockHolding.GetFilter(holding)
  526. .Combine(new Filter<StockMovement>(x => x.JobRequisitionItem.ID).InList(rIDs)),
  527. Columns.None<StockMovement>().Add(x => x.Units).Add(x => x.JobRequisitionItem.ID))
  528. .ToObjects<StockMovement>().GroupBy(x => x.JobRequisitionItem.ID).ToDictionary(x => x.Key, x => x.Sum(x => x.Units));
  529. DoTransfer(holding, items, x => x.ID == Guid.Empty ? x.Qty : quantities.GetValueOrDefault(x.ID), (requi, mThis, mThat) =>
  530. {
  531. mThis.JobRequisitionItem.ID = requi.ID;
  532. mThis.Notes = $"Moved to {target.Code} by {App.EmployeeName}";
  533. mThat.JobRequisitionItem.ID = requi.ID;
  534. mThat.Notes = $"Moved to {target.Code} by {App.EmployeeName}";
  535. mThat.Location.Clear();
  536. mThat.Location.ID = target.ID;
  537. }, updates);
  538. }
  539. SaveBatch(StockMovementBatchType.Transfer, updates);
  540. DoChanged();
  541. Refresh(false, true);
  542. return true;
  543. }
  544. protected override void DoEdit()
  545. {
  546. var holding = SelectedRows.FirstOrDefault()?.ToObject<StockHolding>();
  547. if (holding is null) return;
  548. var movement = CreateMovementFromHolding(holding);
  549. movement.Received = holding.Available;
  550. movement.Type = StockMovementType.TransferIn;
  551. var smg = CheckStockMovementGrid(MovementAction.Transfer, holding);
  552. var result = smg.EditItems(new[] { movement });
  553. var mvts = new List<StockMovement>
  554. {
  555. movement
  556. };
  557. if (result)
  558. {
  559. var other = CreateMovementFromHolding(holding);
  560. other.Issued = movement.Received;
  561. other.Transaction = movement.Transaction;
  562. other.Type = StockMovementType.TransferOut;
  563. var changes = new List<string>();
  564. if (movement.Location.ID != other.Location.ID)
  565. changes.Add(movement.Location.Code);
  566. if (movement.Job.ID != other.Job.ID)
  567. changes.Add(string.IsNullOrEmpty(movement.Job.JobNumber) ? "General Stock" : movement.Job.JobNumber);
  568. if (movement.Style.ID != other.Style.ID)
  569. changes.Add(movement.Style.Code);
  570. //other.Notes = "Transferred to "+String.Join(" / ",changes);
  571. other.Notes = string.Format("Transferred to {0}{1}{2}", string.Join(" / ",
  572. changes.Where(x => !string.IsNullOrWhiteSpace(x))),
  573. string.IsNullOrWhiteSpace(movement.Notes) ? "" : "\n",
  574. string.Join("\n", movement.Notes.Split('\n').Where(x => !x.StartsWith("Transferred from ")))
  575. );
  576. other.System = true;
  577. Client.Save(other, "");
  578. mvts.Add(other);
  579. }
  580. if (result)
  581. {
  582. DoChanged();
  583. SaveBatch(StockMovementBatchType.Transfer, mvts.ToArray());
  584. }
  585. Refresh(false, true);
  586. }
  587. public IStockLocation Location { get; set; }
  588. protected override void SelectItems(CoreRow[]? rows)
  589. {
  590. base.SelectItems(rows);
  591. var nRows = rows?.Length ?? 0;
  592. ReceiveButton.IsEnabled = Location != null && Location.ID != Guid.Empty;
  593. IssueButton.IsEnabled = Location != null && Location.ID != Guid.Empty && nRows > 0;
  594. if(Location is null || Location.ID == Guid.Empty || nRows == 0)
  595. {
  596. TransferButton.IsEnabled = false;
  597. RelocateButton.IsEnabled = false;
  598. }
  599. else if(nRows == 1)
  600. {
  601. TransferButton.IsEnabled = true;
  602. RelocateButton.IsEnabled = true;
  603. }
  604. else
  605. {
  606. TransferButton.IsEnabled = false;
  607. RelocateButton.IsEnabled = true;
  608. }
  609. var _groups = rows?.GroupBy(x => new Tuple<Guid, double>(
  610. x.Get<StockHolding, Guid>(c => c.Product.ID),
  611. x.Get<StockHolding, double>(c => c.Dimensions.Value))
  612. );
  613. AdjustValueButton.IsEnabled = Location != null && Location.ID != Guid.Empty && _groups?.Count() == 1;
  614. RecalculateButton.IsEnabled = Location != null && Location.ID != Guid.Empty;
  615. }
  616. protected override void Reload(
  617. Filters<StockHolding> criteria, Columns<StockHolding> columns, ref SortOrder<StockHolding>? sort,
  618. CancellationToken token, Action<CoreTable?, Exception?> action)
  619. {
  620. ReceiveButton.IsEnabled = Location != null && Location.ID != Guid.Empty;
  621. if (Location == null)
  622. criteria.Add(new Filter<StockHolding>().None());
  623. else
  624. criteria.Add(new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(Location.ID));
  625. base.Reload(criteria, columns, ref sort, token, action);
  626. }
  627. #region Internal Utilities
  628. private StockMovement CreateMovementFromHolding(StockHolding holding)
  629. {
  630. var movement = new StockMovement();
  631. movement.Date = DateTime.Now;
  632. movement.Location.ID = Location.ID;
  633. movement.Location.Code = Location.Code;
  634. movement.Location.Description = Location.Description;
  635. movement.Product.ID = holding.Product.ID;
  636. movement.Job.ID = holding.Job.ID;
  637. movement.Job.JobNumber = holding.Job.JobNumber;
  638. movement.Style.ID = holding.Style.ID;
  639. movement.Style.Code = holding.Style.Code;
  640. movement.Employee.ID = App.EmployeeID;
  641. movement.Dimensions.CopyFrom(holding.Dimensions);
  642. movement.Cost = holding.AverageValue;
  643. movement.CommitChanges();
  644. return movement;
  645. }
  646. private static void SaveBatch(StockMovementBatchType type, IList<StockMovement> movements)
  647. {
  648. var batch = new StockMovementBatch();
  649. batch.Type = type;
  650. batch.Notes = batch.Type + " batch created from Desktop Stock Location Screen";
  651. batch.Employee.ID = App.EmployeeID;
  652. new Client<StockMovementBatch>().Save(batch, "created from Desktop Stock Location Screen");
  653. foreach (var mvt in movements)
  654. {
  655. mvt.Batch.ID = batch.ID;
  656. }
  657. new Client<StockMovement>().Save(movements, "Updating batch from Desktop Stock Location Screen");
  658. }
  659. #endregion
  660. #region StockMovementGrid
  661. private DynamicDataGrid<StockMovement> CheckStockMovementGrid(MovementAction action, StockHolding holding)
  662. {
  663. _action = action;
  664. _holding = holding;
  665. if (smg == null)
  666. {
  667. smg = new DynamicDataGrid<StockMovement>();
  668. smg.OnCustomiseEditor += StockMovementCustomiseEditor;
  669. smg.OnValidate += StockMovementValidate;
  670. smg.OnEditorValueChanged += StockMovementValueChanged;
  671. }
  672. return smg;
  673. }
  674. private Dictionary<string, object?> StockMovementValueChanged(IDynamicEditorForm form, string name, object value)
  675. {
  676. var result = new Dictionary<string, object?>();
  677. if (name.Equals("Location.Job.ID"))
  678. {
  679. var editor = form.FindEditor("Job.ID");
  680. if (!value.Equals(Guid.Empty))
  681. result = DynamicGridUtils.UpdateEditorValue(form.Items, "Job.ID", value);
  682. else
  683. foreach (StockMovement item in form.Items)
  684. result = DynamicGridUtils.UpdateEditorValue(new[] { item }, "Job.ID",
  685. item.Job.HasOriginalValue("ID") ? item.Job.GetOriginalValue(x => x.ID) : item.Job.ID);
  686. editor.IsEnabled = value.Equals(Guid.Empty);
  687. }
  688. return result;
  689. }
  690. private void StockMovementValidate(object sender, StockMovement[] items, List<string> errors)
  691. {
  692. if (items.Any(x => x.Received == 0 && x.Issued == 0))
  693. {
  694. errors.Add("Quantity may not be zero");
  695. }
  696. else if(_action == MovementAction.Issue && _holding is not null && items.Any(x => x.Issued > _holding.Available))
  697. {
  698. errors.Add($"Quantity may not be greater than available stock ({_holding.Available})");
  699. }
  700. if (items.Any(x => x.Product.ID == Guid.Empty))
  701. errors.Add("Product may not be blank");
  702. if (items.Any(x => x.Location.ID == Guid.Empty))
  703. errors.Add("Location may not be blank");
  704. if (!errors.Any() && _action == MovementAction.Transfer)
  705. foreach (var item in items)
  706. {
  707. var changes = new List<string>();
  708. if (item.Location.HasOriginalValue(x => x.ID))
  709. changes.Add(item.Location.GetOriginalValue(x => x.Code));
  710. if (item.Job.HasOriginalValue(x => x.ID))
  711. {
  712. var job = item.Job.GetOriginalValue(x => x.JobNumber);
  713. if (string.IsNullOrEmpty(job))
  714. job = "General Stock";
  715. changes.Add(job);
  716. }
  717. if (item.Style.HasOriginalValue(x => x.ID))
  718. changes.Add(item.Style.GetOriginalValue(x => x.Code));
  719. if (changes.Any())
  720. item.Notes = string.Format("Transferred from {0}{1}{2}",
  721. string.Join(" / ", changes.Where(x => !string.IsNullOrWhiteSpace(x))),
  722. string.IsNullOrWhiteSpace(item.Notes) ? "" : "\n", item.Notes);
  723. else
  724. errors.Add("Transfers must change either Location, Style or Job");
  725. }
  726. }
  727. private void StockMovementCustomiseEditor(IDynamicEditorForm sender, StockMovement[]? items, DynamicGridColumn column, BaseEditor editor)
  728. {
  729. if (column.ColumnName.Equals("Location.ID"))
  730. editor.Editable = _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
  731. if (column.ColumnName.Equals("Product.ID"))
  732. editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Disabled;
  733. if (column.ColumnName.Equals("Product.NettCost"))
  734. editor.Editable = Editable.Hidden;
  735. if (column.ColumnName.Equals("Style.ID"))
  736. editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
  737. if (column.ColumnName.Equals("UnitSize"))
  738. editor.Editable = _action == MovementAction.Receive ? Editable.Enabled : Editable.Hidden;
  739. if (column.ColumnName.Equals("Job.ID"))
  740. editor.Editable = _action == MovementAction.Receive && items?.FirstOrDefault()?.Job.IsValid() == true ? Editable.Disabled : Editable.Enabled;
  741. if (column.ColumnName.Equals(nameof(StockMovement.Received)))
  742. {
  743. editor.Editable = _action == MovementAction.Receive || _action == MovementAction.Transfer ? Editable.Enabled : Editable.Hidden;
  744. editor.Caption = "Quantity";
  745. }
  746. if (column.ColumnName.Equals(nameof(StockMovement.Issued)))
  747. {
  748. editor.Editable = _action == MovementAction.Issue ? Editable.Enabled : Editable.Hidden;
  749. editor.Caption = "Quantity";
  750. }
  751. }
  752. #endregion
  753. }