StockHoldingStore.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. using Comal.Classes;
  2. using InABox.Core;
  3. using InABox.Database;
  4. using System.Linq;
  5. using System;
  6. using MathNet.Numerics;
  7. namespace Comal.Stores;
  8. using HoldingDictionary = Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>;
  9. public class StockHoldingStore : BaseStore<StockHolding>
  10. {
  11. public enum Action
  12. {
  13. Increase,
  14. Decrease
  15. }
  16. public static StockMovement[] LoadMovementData(IStore store, Guid[] ids)
  17. {
  18. return store.Provider.Query(
  19. new Filter<StockMovement>(x => x.ID).InList(ids),
  20. Columns.None<StockMovement>().Add(x => x.ID)
  21. .Add(x => x.Location.ID)
  22. .Add(x => x.Product.ID)
  23. .Add(x => x.Style.ID)
  24. .Add(x => x.Job.ID)
  25. .Add(x => x.Dimensions.Unit.ID)
  26. .Add(x => x.Dimensions.Quantity)
  27. .Add(x => x.Dimensions.Height)
  28. .Add(x => x.Dimensions.Width)
  29. .Add(x => x.Dimensions.Length)
  30. .Add(x => x.Dimensions.Weight)
  31. .Add(x => x.Dimensions.UnitSize)
  32. .Add(x => x.Dimensions.Value)
  33. .Add(x => x.JobRequisitionItem.ID)
  34. .Add(x => x.Units)
  35. .Add(x => x.Cost)
  36. ).ToArray<StockMovement>();
  37. }
  38. public static HoldingDictionary LoadStockHoldings(IStore store, StockMovement[] mvts, HoldingDictionary? holdings = null)
  39. {
  40. if(holdings is not null)
  41. {
  42. mvts = mvts.Where(mvt =>
  43. {
  44. var key = (mvt.Product.ID, mvt.Style.ID, mvt.Location.ID, mvt.Job.ID, mvt.Dimensions);
  45. return !holdings.ContainsKey(key);
  46. }).ToArray();
  47. }
  48. else
  49. {
  50. holdings = new();
  51. }
  52. var productIDs = mvts.Select(x => x.Product.ID).Distinct().ToArray();
  53. var locationIDs = mvts.Select(x => x.Location.ID).Distinct().ToArray();
  54. var styleIDs = mvts.Select(x => x.Style.ID).Distinct().ToArray();
  55. var jobIDs = mvts.Select(x => x.Job.ID).Distinct().ToArray();
  56. var newHoldings = store.Provider.Query(new Filter<StockHolding>(x => x.Product.ID).InList(productIDs)
  57. .And(x => x.Location.ID).InList(locationIDs)
  58. .And(x => x.Style.ID).InList(styleIDs)
  59. .And(x => x.Job.ID).InList(jobIDs),
  60. Columns.None<StockHolding>().Add(x => x.ID)
  61. .Add(x => x.Units)
  62. .Add(x => x.Qty)
  63. .Add(x => x.Value)
  64. .Add(x => x.Available)
  65. .Add(x => x.Weight)
  66. .Add(x => x.AverageValue)
  67. .Add(x => x.Product.ID)
  68. .Add(x => x.Location.ID)
  69. .Add(x => x.Style.ID)
  70. .Add(x => x.Job.ID)
  71. .AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local)
  72. ).ToObjects<StockHolding>();
  73. foreach(var holding in newHoldings)
  74. {
  75. holdings[(holding.Product.ID, holding.Style.ID, holding.Location.ID, holding.Job.ID, holding.Dimensions)] = holding;
  76. }
  77. return holdings;
  78. }
  79. public static void ModifyHoldings(StockMovement[] mvts, HoldingDictionary holdings, Action action)
  80. {
  81. foreach(var mvt in mvts)
  82. {
  83. var key = (mvt.Product.ID, mvt.Style.ID, mvt.Location.ID, mvt.Job.ID, mvt.Dimensions);
  84. var holding = holdings.GetValueOrDefault(key);
  85. if(holding is null)
  86. {
  87. holding = new();
  88. holding.Location.ID = mvt.Location.ID;
  89. holding.Product.ID = mvt.Product.ID;
  90. holding.Style.ID = mvt.Style.ID;
  91. holding.Job.ID = mvt.Job.ID;
  92. holding.Dimensions.CopyFrom(mvt.Dimensions);
  93. holdings[key] = holding;
  94. }
  95. double multiplier = action == Action.Increase ? 1F : -1F;
  96. holding.Units += (multiplier * mvt.Units);
  97. holding.Qty += (multiplier * mvt.Units * mvt.Dimensions.Value);
  98. holding.Value += (multiplier * mvt.Units * mvt.Cost);
  99. holding.Available += (multiplier * (mvt.JobRequisitionItem.ID == Guid.Empty ? mvt.Units : 0.0));
  100. holding.Weight = holding.Qty * holding.Dimensions.Weight;
  101. holding.AverageValue = holding.Units != 0 ? holding.Value / holding.Units : 0.0F;
  102. }
  103. }
  104. public static void SaveHoldings(IStore store, HoldingDictionary holdings)
  105. {
  106. var holdingStore = store.FindSubStore<StockHolding>();
  107. holdingStore.Delete(
  108. holdings.Values.Where(x => x.ID != Guid.Empty && x.Units.IsEffectivelyEqual(0.0) && x.Available.IsEffectivelyEqual(0.0)), "");
  109. holdingStore.Save(
  110. holdings.Values.Where(x => x.IsChanged() && (!x.Units.IsEffectivelyEqual(0.0) || !x.Available.IsEffectivelyEqual(0.0))), "");
  111. }
  112. public static void UpdateStockHoldings(IStore store, Guid[] ids, Action action)
  113. {
  114. var movements = LoadMovementData(store, ids);
  115. var holdings = LoadStockHoldings(store, movements);
  116. ModifyHoldings(movements, holdings, action);
  117. SaveHoldings(store, holdings);
  118. }
  119. /// <summary>
  120. /// Maintains the Stock Holding Table when manipulating Stock Movements
  121. /// We only accept an ID, because the rest of the movement is pulled from the database
  122. /// (slower, but more reliable)
  123. /// </summary>
  124. /// <param name="id">The id of the Stock Movement to query</param>
  125. /// <param name="action">The action to perform (increase / decrease)</param>
  126. public static void UpdateStockHolding(IStore store, Guid id, Action action)
  127. {
  128. var movement = store.Provider.Query(
  129. new Filter<StockMovement>(x => x.ID).IsEqualTo(id),
  130. Columns.None<StockMovement>().Add(x => x.ID)
  131. .Add(x => x.Location.ID)
  132. .Add(x => x.Product.ID)
  133. .Add(x => x.Style.ID)
  134. .Add(x => x.Job.ID)
  135. .Add(x => x.Dimensions.Unit.ID)
  136. .Add(x => x.Dimensions.Quantity)
  137. .Add(x => x.Dimensions.Height)
  138. .Add(x => x.Dimensions.Width)
  139. .Add(x => x.Dimensions.Length)
  140. .Add(x => x.Dimensions.Weight)
  141. .Add(x => x.Dimensions.UnitSize)
  142. .Add(x => x.Dimensions.Value)
  143. .Add(x => x.JobRequisitionItem.ID)
  144. .Add(x => x.Units)
  145. .Add(x => x.Cost)
  146. ).Rows
  147. .FirstOrDefault()?
  148. .ToObject<StockMovement>();
  149. if (movement == null)
  150. return;
  151. var holding = store.Provider.Query(new Filter<StockHolding>(x => x.Product.ID).IsEqualTo(movement.Product.ID)
  152. .And(x => x.Location.ID).IsEqualTo(movement.Location.ID)
  153. .And(x => x.Style.ID).IsEqualTo(movement.Style.ID)
  154. .And(x => x.Job.ID).IsEqualTo(movement.Job.ID)
  155. .And(x => x.Dimensions).DimensionEquals(movement.Dimensions),
  156. Columns.None<StockHolding>().Add(x => x.ID)
  157. .Add(x => x.Units)
  158. .Add(x => x.Qty)
  159. .Add(x => x.Value)
  160. .Add(x => x.Available)
  161. ).Rows
  162. .FirstOrDefault()?
  163. .ToObject<StockHolding>();
  164. if (holding == null)
  165. {
  166. holding = new();
  167. holding.Location.ID = movement.Location.ID;
  168. holding.Product.ID = movement.Product.ID;
  169. holding.Style.ID = movement.Style.ID;
  170. holding.Job.ID = movement.Job.ID;
  171. holding.Dimensions.CopyFrom(movement.Dimensions);
  172. }
  173. double multiplier = action == Action.Increase ? 1F : -1F;
  174. holding.Units += (multiplier * movement.Units);
  175. holding.Qty += (multiplier * movement.Units * movement.Dimensions.Value);
  176. holding.Value += (multiplier * movement.Units * movement.Cost);
  177. holding.Available += (multiplier * (movement.JobRequisitionItem.ID == Guid.Empty ? movement.Units : 0.0));
  178. holding.Weight = holding.Qty * holding.Dimensions.Weight;
  179. holding.AverageValue = holding.Units != 0 ? holding.Value / holding.Units : 0.0F;
  180. // Automagically clean up empty holdings
  181. if (holding.Units.IsEffectivelyEqual(0.0) && holding.Available.IsEffectivelyEqual(0.0))
  182. {
  183. if (holding.ID != Guid.Empty)
  184. DbFactory.NewProvider(Logger.Main).Delete(holding, "");
  185. }
  186. else
  187. DbFactory.NewProvider(Logger.Main).Save(holding);
  188. }
  189. }