StockTransaction.cs 18 KB

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