StockTransaction.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using Comal.Classes;
  6. using HarfBuzzSharp;
  7. using InABox.Core;
  8. using InABox.Mobile;
  9. using Xamarin.CommunityToolkit.ObjectModel;
  10. using Xamarin.Forms;
  11. namespace PRS.Mobile
  12. {
  13. public enum StockTransactionType
  14. {
  15. Transfer,
  16. Issue,
  17. Receive,
  18. StockTake
  19. }
  20. public enum StockTransactionView
  21. {
  22. Source,
  23. Target
  24. }
  25. public enum StockTransactionProperty
  26. {
  27. Location,
  28. Style,
  29. Job
  30. }
  31. public class StockTransactionHolding : BindableObject
  32. {
  33. public StockHoldingShell? Shell { get; set; }
  34. public Guid LocationID { get; set; }
  35. public String LocationCode { get; set; }
  36. public String LocationDescription { get; set; }
  37. public String LocationDisplay => LocationID == Guid.Empty ? "" : $"{LocationCode}: {LocationDescription}";
  38. public Guid StyleID { get; set; }
  39. public String StyleCode { get; set; }
  40. public String StyleDescription { get; set; }
  41. public String StyleDisplay => StyleID == Guid.Empty ? "" : $"{StyleCode}: {StyleDescription}";
  42. public Guid JobID { get; set; }
  43. public String JobNumber { get; set; }
  44. public String JobName { get; set; }
  45. public String JobDisplay => JobID == Guid.Empty ? "" : $"{JobNumber}: {JobName}";
  46. public double Units { get; set; }
  47. public bool HasAllocations => !(Shell?.Units ?? 0.0).IsEffectivelyEqual(Shell?.Available ?? 0.0);
  48. public StockTransactionHolding(StockHoldingShell? shell)
  49. {
  50. Shell = shell;
  51. LocationID = shell?.LocationID ?? Guid.Empty;
  52. LocationCode = shell?.LocationCode ?? string.Empty;
  53. LocationDescription = shell?.LocationDescription ?? string.Empty;
  54. StyleID = shell?.StyleID ?? Guid.Empty;
  55. StyleCode = shell?.StyleCode ?? string.Empty;
  56. StyleDescription = shell?.StyleDescription ?? string.Empty;
  57. JobID = shell?.JobID ?? Guid.Empty;
  58. JobNumber = shell?.JobNumber ?? string.Empty;
  59. JobName = shell?.JobName ?? string.Empty;
  60. Units = shell?.Units ?? 0.0d;
  61. }
  62. }
  63. public class StockTransactionAllocation : BindableObject
  64. {
  65. public Guid ID { get; set; }
  66. public String Description { get; set; }
  67. public double Quantity { get; set; }
  68. public double Maximum { get; set; }
  69. }
  70. public class StockTransaction : BindableObject
  71. {
  72. private double _dimensionsQuantity;
  73. private double _dimensionsLength;
  74. private double _dimensionsWidth;
  75. private double _dimensionsHeight;
  76. private double _dimensionsWeight;
  77. public StockTransactionType Type { get; set; }
  78. public Guid TransactionID { get; private set; }
  79. public Guid EmployeeID { get; private set; }
  80. public Guid ProductID { get; set; }
  81. public String ProductCode { get; set; }
  82. public String ProductName { get; set; }
  83. public String ProductDisplay => ProductID == Guid.Empty ? "" : $"{ProductCode}: {ProductName}";
  84. public Guid ImageID { get; set; }
  85. public byte[]? Image { get; set; }
  86. public Guid DimensionsUnitID { get; set; }
  87. public double DimensionsQuantity
  88. {
  89. get => _dimensionsQuantity;
  90. set
  91. {
  92. _dimensionsQuantity = value;
  93. CalculateDimensions();
  94. }
  95. }
  96. public double DimensionsLength
  97. {
  98. get => _dimensionsLength;
  99. set
  100. {
  101. _dimensionsLength = value;
  102. CalculateDimensions();
  103. }
  104. }
  105. public double DimensionsWidth
  106. {
  107. get => _dimensionsWidth;
  108. set
  109. {
  110. _dimensionsWidth = value;
  111. CalculateDimensions();
  112. }
  113. }
  114. public double DimensionsHeight
  115. {
  116. get => _dimensionsHeight;
  117. set
  118. {
  119. _dimensionsHeight = value;
  120. CalculateDimensions();
  121. }
  122. }
  123. public double DimensionsWeight
  124. {
  125. get => _dimensionsWeight;
  126. set
  127. {
  128. _dimensionsWeight = value;
  129. CalculateDimensions();
  130. }
  131. }
  132. public String DimensionsUnitSize { get; set; }
  133. public double DimensionsValue { get; set; }
  134. public double Cost { get; set; }
  135. public StockTransactionAllocation[]? Allocations { get; set; }
  136. public double Quantity => Allocations?.Aggregate(0.0, (t, a) => t += a.Quantity) ?? 0.0;
  137. public void RefreshQuantity() => OnPropertyChanged(nameof(Quantity));
  138. public bool HasAllocations => Allocations?.Any(x => x.ID != Guid.Empty) ?? false;
  139. public StockTransactionHolding Source { get; private set; }
  140. public StockTransactionHolding Target { get; private set; }
  141. public bool LocationChanged => (Source?.LocationID ?? Guid.Empty) != (Target?.LocationID ?? Guid.Empty);
  142. public bool StyleChanged => (Source?.StyleID ?? Guid.Empty) != (Target?.StyleID ?? Guid.Empty);
  143. public bool JobChanged => (Source?.JobID ?? Guid.Empty) != (Target?.JobID ?? Guid.Empty);
  144. public StockTransaction(StockTransactionType type, StockHoldingShell? source, StockHoldingShell? target)
  145. {
  146. var shell = source ?? target;
  147. Type = type;
  148. TransactionID = Guid.NewGuid();
  149. EmployeeID = App.Data.Me.ID;
  150. ProductID = shell?.ProductID ?? Guid.Empty;
  151. ProductCode = shell?.ProductCode ?? string.Empty;
  152. ProductName = shell?.ProductName ?? string.Empty;
  153. ImageID = shell?.ImageID ?? Guid.Empty;
  154. Image = shell?.Image ?? new byte[] { };
  155. DimensionsUnitID = shell?.DimensionsUnitID ?? Guid.Empty;
  156. DimensionsQuantity = shell?.DimensionsQuantity ?? 0.0;
  157. DimensionsLength = shell?.DimensionsLength ?? 0.0;
  158. DimensionsWidth = shell?.DimensionsWidth ?? 0.0;
  159. DimensionsHeight = shell?.DimensionsHeight ?? 0.0;
  160. DimensionsWeight = shell?.DimensionsWeight ?? 0.0;
  161. DimensionsUnitSize = shell?.DimensionsUnitSize ?? string.Empty;
  162. DimensionsValue = shell?.DimensionsValue ?? 0.0;
  163. Cost = shell?.AverageCost ?? 0.0;
  164. Source = new StockTransactionHolding(source);
  165. Source.PropertyChanged += (sender, args) => OnPropertyChanged(nameof(Source));
  166. Target = new StockTransactionHolding(target);
  167. Target.PropertyChanged += (sender, args) => OnPropertyChanged(nameof(Target));
  168. }
  169. private void CalculateDimensions()
  170. {
  171. var dimensions = App.Data.ProductDimensionUnits.FirstOrDefault(x => x.ID == DimensionsUnitID);
  172. Dictionary<string, object?> variables = new Dictionary<string, object?>();
  173. variables["Length"] = DimensionsLength;
  174. variables["Width"] = DimensionsWidth;
  175. variables["Height"] = DimensionsHeight;
  176. variables["Quantity"] = DimensionsQuantity;
  177. variables["Weight"] = DimensionsHeight;
  178. if (! String.IsNullOrWhiteSpace(dimensions?.Format))
  179. DimensionsUnitSize = new CoreExpression(dimensions.Format).Evaluate(variables)?.ToString() ?? string.Empty;
  180. if (!String.IsNullOrWhiteSpace(dimensions?.Formula))
  181. {
  182. var value = new CoreExpression(dimensions.Format).Evaluate(variables)?.ToString() ?? "0.0";
  183. DimensionsValue = double.TryParse(value, out double val)
  184. ? val
  185. : 0.0;
  186. }
  187. }
  188. public string Display(StockTransactionView? view = null)
  189. {
  190. List<String> results = new List<string>();
  191. if ((Source?.LocationID ?? Guid.Empty) != (Target?.LocationID ?? Guid.Empty))
  192. {
  193. string location = Type switch
  194. {
  195. StockTransactionType.Issue => "Issued",
  196. StockTransactionType.Receive => "Received",
  197. _ => "Transferred"
  198. };
  199. string fromlocation = "";
  200. string tolocation = "";
  201. if ((view == null) || (view == StockTransactionView.Target))
  202. fromlocation = (Source?.LocationID ?? Guid.Empty) != Guid.Empty
  203. ? $" From {Source?.LocationCode ?? "--"}"
  204. : " From --";
  205. if ((view == null) || (view == StockTransactionView.Source))
  206. tolocation = (Target?.LocationID ?? Guid.Empty) != Guid.Empty
  207. ? $" To {Target?.LocationCode ?? "--"}"
  208. : " To --";
  209. results.Add($"{location}{fromlocation}{tolocation}");
  210. }
  211. if ((Source?.StyleID ?? Guid.Empty) != (Target?.StyleID ?? Guid.Empty))
  212. {
  213. string fromstyle = "";
  214. string tostyle = "";
  215. if ((view == null) || (view == StockTransactionView.Target))
  216. fromstyle = (Source?.StyleID ?? Guid.Empty) != Guid.Empty
  217. ? $" From {Source?.StyleCode ?? "(none)"}"
  218. : " From (none)";
  219. if ((view == null) || (view == StockTransactionView.Source))
  220. tostyle = (Target?.StyleID ?? Guid.Empty) != Guid.Empty
  221. ? $" To {Target?.StyleCode ?? "(none)"}"
  222. : " To (none)";
  223. results.Add($"Changed Style{fromstyle}{tostyle}");
  224. }
  225. if ((Source?.JobID ?? Guid.Empty) != (Target?.JobID ?? Guid.Empty))
  226. {
  227. string fromjob = "";
  228. string tojob = "";
  229. if ((view == null) || (view == StockTransactionView.Target))
  230. fromjob = (Source?.JobID ?? Guid.Empty) != Guid.Empty
  231. ? $" From {Source?.JobNumber ?? "(none)"}"
  232. : " From (none)";
  233. if ((view == null) || (view == StockTransactionView.Source))
  234. tojob = (Target?.JobID ?? Guid.Empty) != Guid.Empty
  235. ? $" To {Target?.JobNumber ?? "(none)"}"
  236. : " To (none)";
  237. results.Add($"Changed Job{fromjob}{tojob}");
  238. }
  239. return String.Join("\n", results);
  240. }
  241. }
  242. public class StockTransactionImage : BindableObject
  243. {
  244. public MobileDocument Document { get; private set; }
  245. public byte[] Thumbnail { get; set; }
  246. public StockTransactionImage(MobileDocument document, byte[] thumbnail)
  247. {
  248. Document = document;
  249. Thumbnail = thumbnail;
  250. }
  251. }
  252. public class StockTransactions : ObservableRangeCollection<StockTransaction>
  253. {
  254. private enum TransactionDirection
  255. {
  256. Issue,
  257. Receive
  258. }
  259. public void ProcessTransactions(StockMovementModel model, Guid batchid)
  260. {
  261. void CreateMovement(
  262. StockMovementType type,
  263. StockTransaction transaction,
  264. Guid locationid,
  265. Guid styleid,
  266. Guid jobid,
  267. DateTime timestamp,
  268. TransactionDirection direction,
  269. Action<StockTransaction, StockTransactionAllocation, StockMovementShell>? oncreate = null
  270. )
  271. {
  272. foreach (var allocation in transaction.Allocations)
  273. {
  274. var movement = model.AddItem();
  275. movement.BatchID = batchid;
  276. movement.Date = timestamp;
  277. movement.Type = type;
  278. movement.TransactionID = transaction.TransactionID;
  279. movement.ProductID = transaction.ProductID;
  280. movement.DimensionsUnitID = transaction.DimensionsUnitID;
  281. movement.DimensionsQuantity = transaction.DimensionsQuantity;
  282. movement.DimensionsLength = transaction.DimensionsLength;
  283. movement.DimensionsWidth = transaction.DimensionsWidth;
  284. movement.DimensionsHeight = transaction.DimensionsHeight;
  285. movement.DimensionsWeight = transaction.DimensionsWeight;
  286. movement.DimensionsValue = transaction.DimensionsValue;
  287. movement.DimensionsUnitSize = transaction.DimensionsUnitSize;
  288. movement.LocationID = locationid;
  289. movement.StyleID = styleid;
  290. movement.JobID = jobid;
  291. movement.RequisitionItemID = allocation.ID;
  292. movement.Received = direction == TransactionDirection.Receive ? allocation.Quantity : 0.0F;
  293. movement.Issued = direction == TransactionDirection.Issue ? allocation.Quantity : 0.0F;
  294. movement.Cost = transaction.Cost;
  295. movement.EmployeeID = transaction.EmployeeID;
  296. oncreate?.Invoke(transaction, allocation, movement);
  297. }
  298. }
  299. var now = DateTime.Now;
  300. foreach (var transaction in this)
  301. {
  302. if (transaction.Type == StockTransactionType.Receive)
  303. {
  304. CreateMovement(
  305. StockMovementType.Receive,
  306. transaction,
  307. transaction.Target.LocationID,
  308. transaction.Target.StyleID,
  309. transaction.Target.JobID,
  310. now,
  311. TransactionDirection.Receive
  312. );
  313. }
  314. else if (transaction.Type == StockTransactionType.Issue)
  315. {
  316. if (transaction.Source.StyleID != transaction.Target.StyleID ||
  317. transaction.Source.JobID != transaction.Target.JobID)
  318. {
  319. CreateMovement(
  320. StockMovementType.TransferOut,
  321. transaction,
  322. transaction.Source.LocationID,
  323. transaction.Source.StyleID,
  324. transaction.Source.JobID,
  325. now,
  326. TransactionDirection.Issue);
  327. CreateMovement(
  328. StockMovementType.TransferIn,
  329. transaction,
  330. transaction.Source.LocationID,
  331. transaction.Target.StyleID,
  332. transaction.Target.JobID,
  333. now,
  334. TransactionDirection.Receive);
  335. }
  336. CreateMovement(
  337. StockMovementType.Issue,
  338. transaction,
  339. transaction.Source.LocationID,
  340. transaction.Target.StyleID,
  341. transaction.Target.JobID,
  342. now,
  343. TransactionDirection.Issue);
  344. }
  345. else if (transaction.Type == StockTransactionType.Transfer)
  346. {
  347. CreateMovement(
  348. StockMovementType.TransferOut,
  349. transaction,
  350. transaction.Source.LocationID,
  351. transaction.Source.StyleID,
  352. transaction.Source.JobID,
  353. now,
  354. TransactionDirection.Issue);
  355. CreateMovement(
  356. StockMovementType.TransferIn,
  357. transaction,
  358. transaction.Target.LocationID,
  359. transaction.Target.StyleID,
  360. transaction.Target.JobID,
  361. now,
  362. TransactionDirection.Receive);
  363. }
  364. else if (transaction.Type == StockTransactionType.StockTake)
  365. {
  366. CreateMovement(
  367. StockMovementType.StockTake,
  368. transaction,
  369. transaction.Target.LocationID,
  370. transaction.Target.StyleID,
  371. transaction.Target.JobID,
  372. now,
  373. transaction.Quantity > 0 ? TransactionDirection.Receive : TransactionDirection.Issue,
  374. (t, a, m) =>
  375. {
  376. var srcAlloc = t.Source.Shell.Allocations.FirstOrDefault(x => x.ID == a.ID);
  377. m.Issued = Math.Max(0, (srcAlloc?.Quantity ?? t.Source.Units) - a.Quantity);
  378. m.Received = Math.Max(0, a.Quantity - (srcAlloc?.Quantity ?? t.Source.Units));
  379. m.Notes = m.Issued.IsEffectivelyEqual(0.0F) && m.Received.IsEffectivelyEqual(0.0F)
  380. ? $"Correct Quantity Confirmed"
  381. : $"Quantity Adjusted from {(srcAlloc?.Quantity ?? t.Source.Units):F2} to {a.Quantity:F2}";
  382. m.Balance = a.Quantity;
  383. }
  384. );
  385. }
  386. }
  387. }
  388. public IEnumerable<StockTransaction> Get(StockHoldingShell shell)
  389. {
  390. bool IsHoldingReferenced(StockTransactionHolding holding) =>
  391. holding.LocationID == shell.LocationID
  392. && holding.StyleID == shell.StyleID
  393. && holding.JobID == shell.JobID;
  394. var results = this.Where(x => x.ProductID == shell.ProductID
  395. && String.Equals(x.DimensionsUnitSize, shell.DimensionsUnitSize)
  396. && (IsHoldingReferenced(x.Source) || IsHoldingReferenced(x.Target))).ToArray();
  397. return results;
  398. }
  399. }
  400. }