StockHoldingGrid.cs 29 KB

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