StockHolding.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using InABox.Core;
  6. using PRSClasses;
  7. namespace Comal.Classes
  8. {
  9. // public class StockHoldingUnitAggregate : CoreAggregate<StockHolding, StockMovement, double>
  10. // {
  11. // public override Expression<Func<StockMovement, double>> Aggregate => x => x.Units;
  12. //
  13. // public override Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>> Links =>
  14. // new Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>>()
  15. // {
  16. // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID },
  17. // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID },
  18. // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
  19. // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
  20. // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
  21. // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
  22. // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
  23. // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
  24. // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
  25. // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
  26. // };
  27. //
  28. // public override AggregateCalculation Calculation => AggregateCalculation.Sum;
  29. // }
  30. //
  31. // public class StockHoldingValueAggregate : CoreAggregate<StockHolding, StockMovement, double>
  32. // {
  33. // public override Expression<Func<StockMovement, double>> Aggregate => x => x.Value;
  34. //
  35. // public override Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>> Links =>
  36. // new Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>>()
  37. // {
  38. // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID },
  39. // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID },
  40. // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
  41. // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
  42. // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
  43. // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
  44. // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
  45. // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
  46. // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
  47. // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
  48. // };
  49. //
  50. // public override AggregateCalculation Calculation => AggregateCalculation.Sum;
  51. // }
  52. //
  53. // public class StockHoldingAverageValueFormula : IFormula<StockHolding, double>
  54. // {
  55. // public Expression<Func<StockHolding, double>> Value => x => x.Value;
  56. //
  57. // public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[] { x => x.Units };
  58. //
  59. // public FormulaOperator Operator => FormulaOperator.Divide;
  60. //
  61. // public FormulaType Type => FormulaType.Virtual;
  62. // }
  63. //
  64. // public class StockHoldingQuantityAggregate : CoreAggregate<StockHolding, StockMovement, double>
  65. // {
  66. // public override Expression<Func<StockMovement, double>> Aggregate => x => x.Qty;
  67. //
  68. // public override Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>> Links =>
  69. // new Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>>()
  70. // {
  71. // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID },
  72. // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID },
  73. // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
  74. // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
  75. // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
  76. // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
  77. // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
  78. // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
  79. // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
  80. // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
  81. // };
  82. //
  83. // public override AggregateCalculation Calculation => AggregateCalculation.Sum;
  84. // }
  85. // public class StockHoldingAvailableAggregate : CoreAggregate<StockHolding, StockMovement, double>
  86. // {
  87. // public override Expression<Func<StockMovement, double>> Aggregate => x => x.Units;
  88. //
  89. // public override Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>> Links =>
  90. // new Dictionary<Expression<Func<StockMovement, object>>, Expression<Func<StockHolding, object>>>()
  91. // {
  92. // { StockMovement => StockMovement.Product.ID, StockHolding => StockHolding.Product.ID },
  93. // { StockMovement => StockMovement.Job.ID, StockHolding => StockHolding.Job.ID },
  94. // { StockMovement => StockMovement.Location.ID, StockHolding => StockHolding.Location.ID },
  95. // { StockMovement => StockMovement.Style.ID, StockHolding => StockHolding.Style.ID },
  96. // { StockMovement => StockMovement.Dimensions.Unit.ID, StockHolding => StockHolding.Dimensions.Unit.ID },
  97. // { StockMovement => StockMovement.Dimensions.Quantity, StockHolding => StockHolding.Dimensions.Quantity },
  98. // { StockMovement => StockMovement.Dimensions.Length, StockHolding => StockHolding.Dimensions.Length },
  99. // { StockMovement => StockMovement.Dimensions.Width, StockHolding => StockHolding.Dimensions.Width },
  100. // { StockMovement => StockMovement.Dimensions.Height, StockHolding => StockHolding.Dimensions.Height },
  101. // { StockMovement => StockMovement.Dimensions.Weight, StockHolding => StockHolding.Dimensions.Weight },
  102. // };
  103. //
  104. // public override AggregateCalculation Calculation => AggregateCalculation.Sum;
  105. //
  106. // public override Filter<StockMovement>? Filter => new Filter<StockMovement>(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty);
  107. // }
  108. // public class StockHoldingWeightFormula : IFormula<StockHolding, double>
  109. // {
  110. // public Expression<Func<StockHolding, double>> Value => x => x.Qty;
  111. //
  112. // public Expression<Func<StockHolding, double>>[] Modifiers => new Expression<Func<StockHolding, double>>[] { x => x.Dimensions.Weight };
  113. //
  114. // public FormulaOperator Operator => FormulaOperator.Multiply;
  115. //
  116. // public FormulaType Type => FormulaType.Virtual;
  117. // }
  118. //
  119. // public class StockHoldingIsRemnantCondition : ICondition<StockHolding, double, object>
  120. // {
  121. // public Expression<Func<StockHolding, double>> Left => x => x.Dimensions.Value;
  122. //
  123. // public Condition Condition => Condition.LessThan;
  124. //
  125. // public Expression<Func<StockHolding, double>> Right => x => x.Product.DefaultInstance.Dimensions.Value;
  126. //
  127. // public Expression<Func<StockHolding, object>> True => x => true;
  128. //
  129. // public Expression<Func<StockHolding, object>> False => x => null;
  130. //
  131. // public ConditionType Type => ConditionType.Virtual;
  132. // }
  133. // public class StockHoldingUnionGenerator : AutoEntityUnionGenerator<IStockHolding>
  134. // {
  135. // protected override void Configure()
  136. // {
  137. // AddTable<StockMovement>();
  138. // }
  139. //
  140. // public override bool Distinct => true;
  141. //
  142. // public override Column<IStockHolding>[] IDColumns => Columns;
  143. //
  144. // public static Column<IStockHolding>[] Columns => new Column<IStockHolding>[]
  145. // {
  146. // new Column<IStockHolding>(x => x.Job.ID),
  147. // new Column<IStockHolding>(x => x.Location.ID),
  148. // new Column<IStockHolding>(x => x.Product.ID),
  149. // new Column<IStockHolding>(x => x.Style.ID),
  150. // new Column<IStockHolding>(x => x.Dimensions.Unit.ID),
  151. // new Column<IStockHolding>(x => x.Dimensions.Quantity),
  152. // new Column<IStockHolding>(x => x.Dimensions.Length),
  153. // new Column<IStockHolding>(x => x.Dimensions.Width),
  154. // new Column<IStockHolding>(x => x.Dimensions.Height),
  155. // new Column<IStockHolding>(x => x.Dimensions.Weight),
  156. // };
  157. //
  158. // public static Filter<StockMovement>? GetFilter(IStockHolding holding)
  159. // {
  160. // var filter = new Filters<StockMovement>();
  161. //
  162. // foreach(var column in Columns)
  163. // {
  164. // filter.Add(new Filter<StockMovement>(column.Cast<StockMovement>()).IsEqualTo(CoreUtils.GetPropertyValue(holding, column.Property)));
  165. // }
  166. //
  167. // return filter.Combine();
  168. // }
  169. // }
  170. //
  171. // [UserTracking(typeof(StockMovement))]
  172. // [AutoEntity(typeof(StockHoldingUnionGenerator))]
  173. // public class StockHoldingView : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>,
  174. // IStockHolding, ILicense<WarehouseLicense>
  175. // {
  176. //
  177. // public override ProductLink Product { get; set; }
  178. //
  179. // public ProductStyleLink Style { get; set; }
  180. //
  181. // public StockLocationLink Location { get; set; }
  182. //
  183. // public JobLink Job { get; set; }
  184. //
  185. // [RequiredColumn]
  186. // [DimensionsEditor(typeof(StockDimensions), AllowEditingUnit = false)]
  187. // public override StockDimensions Dimensions { get; set; }
  188. //
  189. // [Condition(typeof(StockHoldingIsRemnantCondition))]
  190. // [NullEditor]
  191. // public bool IsRemnant { get; set; }
  192. //
  193. // [Aggregate(typeof(StockHoldingUnitAggregate))]
  194. // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  195. // public double Units { get; set; }
  196. //
  197. // [Aggregate(typeof(StockHoldingQuantityAggregate))]
  198. // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  199. // public double Qty { get; set; }
  200. //
  201. // [Formula(typeof(StockHoldingWeightFormula))]
  202. // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  203. // public double Weight { get; set; }
  204. //
  205. // [Aggregate(typeof(StockHoldingValueAggregate))]
  206. // [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  207. // public double Value { get; set; }
  208. //
  209. // [Formula(typeof(StockHoldingAverageValueFormula))]
  210. // [DoubleEditor(Editable = Editable.Disabled)]
  211. // public double AverageValue { get; set; }
  212. // }
  213. // FV 6/2/24: This used to be a view (see above), but we were running into performance issues trying to
  214. // get aggregates fof stock locations (ie show me how many holdings in a given location was taking upwrds of
  215. // 20 seconds. Have moved update logic to StockHoldingStore / StockMovementStore so updates are a bit slower,
  216. // but reads are now much faster.
  217. [UserTracking(typeof(StockMovement))]
  218. public class StockHolding : StockEntity, IRemotable, IPersistent, IOneToMany<StockLocation>, IOneToMany<Product>,
  219. IStockHolding, ILicense<WarehouseLicense>
  220. {
  221. [Editable(Editable.Disabled)]
  222. [EditorSequence(1)]
  223. public StockLocationLink Location { get; set; }
  224. private class ProductLookupGenerator : LookupDefinitionGenerator<Product, StockHolding>
  225. {
  226. public override Filter<Product>? DefineFilter(StockHolding[] items)
  227. => LookupFactory.DefineFilter<Product>().And(x => x.NonStock).IsEqualTo(false);
  228. }
  229. [Editable(Editable.Disabled)]
  230. [EditorSequence(2)]
  231. [LookupDefinition(typeof(ProductLookupGenerator))]
  232. public override ProductLink Product { get; set; }
  233. [DimensionsEditor(typeof(StockDimensions), AllowEditingUnit = false)]
  234. [Editable(Editable.Disabled)]
  235. [EditorSequence(3)]
  236. public override StockDimensions Dimensions { get; set; }
  237. [Editable(Editable.Disabled)]
  238. [EditorSequence(4)]
  239. public ProductStyleLink Style { get; set; }
  240. [Editable(Editable.Disabled)]
  241. [EditorSequence(4)]
  242. public JobLink Job { get; set; }
  243. [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  244. [EditorSequence(5)]
  245. public double Units { get; set; }
  246. [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  247. [EditorSequence(6)]
  248. public double Qty { get; set; }
  249. [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  250. [EditorSequence(7)]
  251. public double Weight { get; set; }
  252. [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  253. [EditorSequence(8)]
  254. public double Value { get; set; }
  255. [DoubleEditor(Editable = Editable.Disabled)]
  256. [EditorSequence(9)]
  257. public double AverageValue { get; set; }
  258. [DoubleEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  259. [EditorSequence(10)]
  260. public double Available { get; set; }
  261. public static Column<IStockHolding>[] Columns => new Column<IStockHolding>[]
  262. {
  263. new Column<IStockHolding>(x => x.Job.ID),
  264. new Column<IStockHolding>(x => x.Location.ID),
  265. new Column<IStockHolding>(x => x.Product.ID),
  266. new Column<IStockHolding>(x => x.Style.ID),
  267. new Column<IStockHolding>(x => x.Dimensions.Unit.ID),
  268. new Column<IStockHolding>(x => x.Dimensions.Quantity),
  269. new Column<IStockHolding>(x => x.Dimensions.Length),
  270. new Column<IStockHolding>(x => x.Dimensions.Width),
  271. new Column<IStockHolding>(x => x.Dimensions.Height),
  272. new Column<IStockHolding>(x => x.Dimensions.Weight),
  273. };
  274. public static Filter<StockMovement>? GetFilter(IStockHolding holding)
  275. {
  276. var filter = new Filters<StockMovement>();
  277. foreach(var column in Columns)
  278. {
  279. filter.Add(new Filter<StockMovement>(column.Cast<StockMovement>()).IsEqualTo(CoreUtils.GetPropertyValue(holding, column.Property)));
  280. }
  281. return filter.Combine();
  282. }
  283. }
  284. public static class StockHoldingExtensions
  285. {
  286. public static bool IsEqualTo(this IStockHolding h1, IStockHolding h2)
  287. {
  288. return h1.Product.ID == h2.Product.ID
  289. && h1.Location.ID == h2.Location.ID
  290. && h1.Job.ID == h2.Job.ID
  291. && h1.Style.ID == h2.Style.ID
  292. && h1.Dimensions.Unit.ID == h2.Dimensions.Unit.ID
  293. && h1.Dimensions.Length.IsEffectivelyEqual(h2.Dimensions.Length)
  294. && h1.Dimensions.Width.IsEffectivelyEqual(h2.Dimensions.Width)
  295. && h1.Dimensions.Height.IsEffectivelyEqual(h2.Dimensions.Height)
  296. && h1.Dimensions.Quantity.IsEffectivelyEqual(h2.Dimensions.Quantity)
  297. && h1.Dimensions.Weight.IsEffectivelyEqual(h2.Dimensions.Weight);
  298. }
  299. public static void Recalculate(this StockHolding holding, IEnumerable<StockMovement> movements)
  300. {
  301. movements = movements.AsIList();
  302. var units = movements.Sum(x => x.Units);
  303. var cost = movements.Select(x => x.Units * x.Cost).Sum();
  304. var available = movements.Where(x => x.JobRequisitionItem.ID == Guid.Empty).Sum(x => x.Units);
  305. holding.Units = units;
  306. holding.AverageValue = units.IsEffectivelyEqual(0.0F) ? 0.0d : cost / units;
  307. holding.Available = available;
  308. }
  309. }
  310. }