StockHoldingGrid.cs 30 KB

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