StockHoldingExtensions.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using InABox.Clients;
  5. using InABox.Core;
  6. namespace Comal.Classes
  7. {
  8. public static class StockHoldingExtensions
  9. {
  10. /// <summary>
  11. /// Create a new stock movement from an <see cref="IStockHolding"/>, copying across the "key" properties;
  12. /// that is, the job, product, style, location and dimensions.
  13. /// </summary>
  14. /// <remarks>
  15. /// Also sets the <see cref="StockMovement.Date"/> to today.
  16. /// </remarks>
  17. /// <param name="holding"></param>
  18. /// <returns></returns>
  19. public static StockMovement CreateMovement(this IStockHolding holding)
  20. {
  21. var movement = new StockMovement();
  22. movement.Date = DateTime.Now;
  23. movement.Job.ID = holding.Job.ID;
  24. movement.Job.Synchronise(holding.Job);
  25. movement.Product.ID = holding.Product.ID;
  26. movement.Product.Synchronise(holding.Product);
  27. movement.Style.ID = holding.Style.ID;
  28. movement.Style.Synchronise(holding.Style);
  29. movement.Location.ID = holding.Location.ID;
  30. movement.Location.Synchronise(holding.Location);
  31. movement.Dimensions.CopyFrom(holding.Dimensions);
  32. return movement;
  33. }
  34. public static List<StockHolding> GroupMovements(IEnumerable<StockMovement> movements)
  35. {
  36. var grouped = new List<StockHolding>();
  37. var toGroup = movements.AsList();
  38. while (toGroup.Count > 0)
  39. {
  40. var first = toGroup.First();
  41. var selected = toGroup.Where(x => x.IsEqualTo(first)).ToList();
  42. var holding = grouped.FirstOrDefault(x => x.IsEqualTo(first));
  43. if (holding == null)
  44. {
  45. holding = new StockHolding();
  46. holding.Location.CopyFrom(first.Location);
  47. holding.Product.CopyFrom(first.Product);
  48. holding.Style.CopyFrom(first.Style);
  49. holding.Job.CopyFrom(first.Job);
  50. holding.Dimensions.CopyFrom(first.Dimensions);
  51. grouped.Add(holding);
  52. }
  53. holding.Recalculate(selected);
  54. toGroup.RemoveAll(x => selected.Any(s => s.ID == x.ID));
  55. }
  56. return grouped;
  57. }
  58. public static bool IsEqualTo(this IStockHolding row1, IStockHolding row2)
  59. {
  60. return row1.Product.ID == row2.Product.ID
  61. && row1.Location.ID == row2.Location.ID
  62. && row1.Job.ID == row2.Job.ID
  63. && row1.Style.ID == row2.Style.ID
  64. && row1.Dimensions.Unit.ID == row2.Dimensions.Unit.ID
  65. && row1.Dimensions.Length.IsEffectivelyEqual(row2.Dimensions.Length)
  66. && row1.Dimensions.Width.IsEffectivelyEqual(row2.Dimensions.Width)
  67. && row1.Dimensions.Height.IsEffectivelyEqual(row2.Dimensions.Height)
  68. && row1.Dimensions.Quantity.IsEffectivelyEqual(row2.Dimensions.Quantity)
  69. && row1.Dimensions.Weight.IsEffectivelyEqual(row2.Dimensions.Weight);
  70. }
  71. public static bool IsEqualTo<T1,T2>(this CoreRow row1, CoreRow row2)
  72. where T1 : IStockHolding
  73. where T2 : IStockHolding
  74. {
  75. return row1.Get<T1,Guid>(x=>x.Product.ID) == row2.Get<T2,Guid>(x => x.Product.ID)
  76. && row1.Get<T1,Guid>(x=>x.Location.ID) == row2.Get<T2,Guid>(x => x.Location.ID)
  77. && row1.Get<T1,Guid>(x=>x.Job.ID) == row2.Get<T2,Guid>(x => x.Job.ID)
  78. && row1.Get<T1,Guid>(x=>x.Style.ID) == row2.Get<T2,Guid>(x => x.Style.ID)
  79. && row1.Get<T1,Guid>(x=>x.Dimensions.Unit.ID) == row2.Get<T2,Guid>(x => x.Dimensions.Unit.ID)
  80. && row1.Get<T1,double>(x=>x.Dimensions.Length).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Length))
  81. && row1.Get<T1,double>(x=>x.Dimensions.Width).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Width))
  82. && row1.Get<T1,double>(x=>x.Dimensions.Height).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Height))
  83. && row1.Get<T1,double>(x=>x.Dimensions.Quantity).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Quantity))
  84. && row1.Get<T1,double>(x=>x.Dimensions.Weight).IsEffectivelyEqual(row2.Get<T2,double>(x=>x.Dimensions.Weight));
  85. }
  86. public static void Recalculate(this StockHolding holding, IEnumerable<StockMovement> movements)
  87. {
  88. movements = movements.AsIList();
  89. var units = movements.Sum(x => x.Units);
  90. var cost = movements.Select(x => x.Units * x.Cost).Sum();
  91. var available = movements.Where(x => x.JobRequisitionItem.ID == Guid.Empty).Sum(x => x.Units);
  92. holding.Units = units;
  93. holding.Available = available;
  94. holding.Qty = movements.Sum(x => x.Units * x.Dimensions.Value);
  95. holding.Value = cost;
  96. holding.AverageValue = units.IsEffectivelyEqual(0.0F) ? 0.0d : cost / units;
  97. holding.Weight = holding.Qty * holding.Dimensions.Weight;
  98. }
  99. public static IEnumerable<JobRequisitionItem> LoadRequisitionItems(this StockHolding holding, bool alwaysshowunallocated = false, Columns<JobRequisitionItem>? columns = null)
  100. {
  101. columns ??= Columns.None<JobRequisitionItem>();
  102. columns.Add(x => x.ID)
  103. .Add(x => x.Product.ID)
  104. .Add(x => x.Style.ID)
  105. .AddDimensionsColumns(x => x.Dimensions)
  106. .Add(x => x.Job.ID)
  107. .Add(x => x.Job.JobNumber)
  108. .Add(x => x.Job.Name)
  109. .Add(x => x.Requisition.Number)
  110. .Add(x => x.Requisition.Description)
  111. .Add(x => x.Qty);
  112. var items = new Client<JobRequisitionItem>().Query(
  113. new Filter<JobRequisitionItem>(x => x.ID).InQuery(StockHolding.GetFilter(holding), x => x.JobRequisitionItem.ID),
  114. columns)
  115. .ToObjects<JobRequisitionItem>()
  116. .Where(x=>x.Product.ID == holding.Product.ID && x.Style.ID == holding.Style.ID && x.Dimensions.Equals(holding.Dimensions));
  117. if (holding.Available > 0 || alwaysshowunallocated)
  118. {
  119. var requi = new JobRequisitionItem() { Qty = holding.Available };
  120. requi.Requisition.Description = "Unallocated Items";
  121. items = CoreUtils.One(requi).Concat(items);
  122. }
  123. return items;
  124. }
  125. public static IEnumerable<StockMovement> AdjustValue(this StockHolding holding, double unitvalue, StockMovementBatch batch)
  126. {
  127. List<StockMovement> _result = new List<StockMovement>();
  128. var movements = Client.Query(
  129. new Filter<StockMovement>(x => x.Location.ID).IsEqualTo(holding.Location.ID)
  130. .And(x=>x.Product.ID).IsEqualTo(holding.Product.ID)
  131. .And(x => x.Style.ID).IsEqualTo(holding.Style.ID)
  132. .And(x => x.Dimensions).DimensionEquals(holding.Dimensions)
  133. .And(x => x.Job.ID).IsEqualTo(holding.Job.ID),
  134. Columns.Required<StockMovement>().Add(x=>x.Units)
  135. ).Rows.ToObjects<StockMovement>().ToArray();
  136. var _allocations = movements.GroupBy(x => x.JobRequisitionItem.ID);
  137. foreach (var _allocation in _allocations)
  138. {
  139. var _units = _allocation.Sum(x => x.Units);
  140. if (!_units.IsEffectivelyEqual(0.0))
  141. {
  142. var _transout = holding.CreateMovement();
  143. _transout.Employee.ID = batch.Employee.ID;
  144. _transout.Issued = _units;
  145. _transout.Cost = holding.AverageValue;
  146. _transout.Type = StockMovementType.TransferOut;
  147. _transout.JobRequisitionItem.ID = _allocation.Key;
  148. _transout.Batch.ID = batch.ID;
  149. _transout.Notes = $"Adjusting Average Value from ${holding.AverageValue:F2} to ${unitvalue:F2}";
  150. _result.Add(_transout);
  151. var _transin = holding.CreateMovement();
  152. _transin.Date = _transout.Date.AddTicks(1);
  153. _transout.Employee.ID = batch.Employee.ID;
  154. _transin.Received = _units;
  155. _transin.Cost = unitvalue;
  156. _transin.Type = StockMovementType.TransferIn;
  157. _transin.Transaction = _transout.Transaction;
  158. _transin.JobRequisitionItem.ID = _allocation.Key;
  159. _transin.Batch.ID = batch.ID;
  160. _transin.Notes = $"Adjusting Average Value from ${holding.AverageValue:F2} to ${unitvalue:F2}";
  161. _result.Add(_transin);
  162. }
  163. }
  164. return _result;
  165. }
  166. public static Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding> LoadStockHoldings(IEnumerable<IStockHolding> mvts, Columns<StockHolding> columns, Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>? holdings = null, IQueryProvider<StockHolding>? query = null)
  167. {
  168. query ??= Client<StockHolding>.Provider;
  169. columns.Add(x => x.ID);
  170. columns.Add(x => x.Product.ID);
  171. columns.Add(x => x.Location.ID);
  172. columns.Add(x => x.Style.ID);
  173. columns.Add(x => x.Job.ID);
  174. columns.AddDimensionsColumns(x => x.Dimensions, Dimensions.ColumnsType.Local);
  175. if(holdings != null)
  176. {
  177. mvts = mvts.Where(mvt =>
  178. {
  179. var key = (mvt.Product.ID, mvt.Style.ID, mvt.Location.ID, mvt.Job.ID, mvt.Dimensions);
  180. return !holdings.ContainsKey(key);
  181. }).ToArray();
  182. }
  183. else
  184. {
  185. holdings = new Dictionary<(Guid product, Guid style, Guid location, Guid job, StockDimensions dimensions), StockHolding>();
  186. }
  187. var productIDs = mvts.Select(x => x.Product.ID).Distinct().ToArray();
  188. var locationIDs = mvts.Select(x => x.Location.ID).Distinct().ToArray();
  189. var styleIDs = mvts.Select(x => x.Style.ID).Distinct().ToArray();
  190. var jobIDs = mvts.Select(x => x.Job.ID).Distinct().ToArray();
  191. var newHoldings = query.Query(new Filter<StockHolding>(x => x.Product.ID).InList(productIDs)
  192. .And(x => x.Location.ID).InList(locationIDs)
  193. .And(x => x.Style.ID).InList(styleIDs)
  194. .And(x => x.Job.ID).InList(jobIDs),
  195. columns
  196. ).ToObjects<StockHolding>();
  197. foreach(var holding in newHoldings)
  198. {
  199. holdings[(holding.Product.ID, holding.Style.ID, holding.Location.ID, holding.Job.ID, holding.Dimensions)] = holding;
  200. }
  201. return holdings;
  202. }
  203. }
  204. }