RequisitionStore.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using Comal.Classes;
  6. using InABox.Core;
  7. namespace Comal.Stores
  8. {
  9. internal class RequisitionStore : BaseStore<Requisition>
  10. {
  11. private readonly bool _debug = true;
  12. private void Log(string format, params object[] values)
  13. {
  14. if (_debug)
  15. Logger.Send(LogType.Information, UserID, string.Format("- RequisitionStore:" + format, values));
  16. }
  17. private bool NeedsUpdating<T>(Requisition entity, Expression<Func<Requisition, T>> property)
  18. {
  19. // If this is a new Requisition, we don't need to do anything
  20. if (entity.HasOriginalValue(x => x.ID))
  21. {
  22. var originalid = entity.GetOriginalValue(x => x.ID);
  23. if (originalid == Guid.Empty)
  24. {
  25. Log("NeedsUpdating() return false - original id is empty");
  26. return false;
  27. }
  28. }
  29. // if the Property has not changed, we don't need to do anything
  30. if (!entity.HasOriginalValue(property))
  31. {
  32. Log("NeedsUpdating() return false - {0} has not changed", property.ToString());
  33. return false;
  34. }
  35. return true;
  36. }
  37. private bool LoadRequisitionItems(Requisition entity, ref IList<RequisitionItem> requisitionitems)
  38. {
  39. requisitionitems ??= Provider.Query(
  40. new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID)
  41. ).ToList<RequisitionItem>();
  42. return requisitionitems.Any();
  43. }
  44. private bool LoadDeliveryItems(Requisition entity, ref IList<DeliveryItem> deliveryitems)
  45. {
  46. deliveryitems ??= Provider.Query(
  47. new Filter<DeliveryItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID),
  48. new Columns<DeliveryItem>(x => x.ID, x => x.DeliveredDate)
  49. ).ToList<DeliveryItem>();
  50. return deliveryitems.Any();
  51. }
  52. #region TakenBy
  53. private void UpdateTakenBy(Requisition entity, ref IList<RequisitionItem> items, ref IList<DeliveryItem> deliveryitems)
  54. {
  55. Log("UpdateTakenBy() - starting");
  56. if (!NeedsUpdating(entity, x => x.TakenBy))
  57. return;
  58. if (!LoadDeliveryItems(entity, ref deliveryitems))
  59. {
  60. Log("UpdateTakenBy() - no delivery items to update");
  61. return;
  62. }
  63. foreach (var deliveryitem in deliveryitems)
  64. if (entity.TakenBy.IsValid() && deliveryitem.DeliveredDate.IsEmpty())
  65. {
  66. Log("UpdateTakenBy() - Setting DeliveryDate");
  67. deliveryitem.DeliveredDate = DateTime.Now;
  68. }
  69. else if (!entity.TakenBy.IsValid() && !deliveryitem.DeliveredDate.IsEmpty())
  70. {
  71. Log("UpdateTakenBy() - Clearing DeliveryDate");
  72. deliveryitem.DeliveredDate = DateTime.MinValue;
  73. }
  74. var updates = deliveryitems.Where(x => x.IsChanged());
  75. if (updates.Any())
  76. FindSubStore<DeliveryItem>().Save(updates,
  77. entity.TakenBy.IsValid() ? "Requisition taken by " + entity.TakenBy.Code : "Requisition [TakenBy] has been cleared");
  78. Log("UpdateTakenBy() - done");
  79. }
  80. #endregion
  81. protected override void BeforeSave(Requisition entity)
  82. {
  83. base.BeforeSave(entity);
  84. if (entity.TakenBy.IsValid() || entity.Delivery.Completed != DateTime.MinValue || entity.Delivery.Delivered != DateTime.MinValue)
  85. {
  86. if (entity.Archived.IsEmpty())
  87. entity.Archived = DateTime.Now;
  88. }
  89. else
  90. {
  91. if (!entity.Archived.IsEmpty())
  92. entity.Archived = DateTime.MinValue;
  93. }
  94. }
  95. protected override void AfterSave(Requisition entity)
  96. {
  97. base.AfterSave(entity);
  98. IList<RequisitionItem> requisitionitems = null;
  99. IList<DeliveryItem> deliveryitems = null;
  100. UpdateDeliveryItems(entity, ref requisitionitems, ref deliveryitems);
  101. UpdateTakenBy(entity, ref requisitionitems, ref deliveryitems);
  102. UpdateStockBatches(entity, ref requisitionitems);
  103. UpdateTrackingKanban<RequisitionKanban, Requisition, RequisitionLink>(entity, e =>
  104. {
  105. if (!entity.Archived.Equals(DateTime.MinValue) || entity.TakenBy.IsValid())
  106. return KanbanStatus.Complete;
  107. if (entity.Delivery.IsValid())
  108. {
  109. if (entity.Delivery.Completed != DateTime.MinValue)
  110. {
  111. return KanbanStatus.Complete;
  112. }
  113. }
  114. if (!entity.Filled.Equals(DateTime.MinValue))
  115. return KanbanStatus.Waiting;
  116. if (Provider.Query(
  117. new Filter<RequisitionItem>(x => x.RequisitionLink.ID).IsEqualTo(entity.ID),
  118. new Columns<RequisitionItem>(x => x.ID)
  119. ).Rows.Any()
  120. )
  121. return KanbanStatus.InProgress;
  122. return KanbanStatus.Open;
  123. });
  124. }
  125. protected override void BeforeDelete(Requisition entity)
  126. {
  127. UnlinkTrackingKanban<RequisitionKanban, Requisition, RequisitionLink>(entity);
  128. }
  129. #region Delivery Items
  130. private void CreateDeliveryItems(Requisition entity, ref IList<RequisitionItem> requisitionitems,
  131. ref IList<DeliveryItem> deliveryitems)
  132. {
  133. if (!LoadRequisitionItems(entity, ref requisitionitems))
  134. {
  135. Log("CreateDeliveryItems() - no requisition items to update");
  136. return;
  137. }
  138. var updates = new List<DeliveryItem>();
  139. foreach (var item in requisitionitems)
  140. updates.Add(item.CreateDeliveryItem(entity));
  141. if (updates.Any())
  142. FindSubStore<DeliveryItem>().Save(updates, "Requisition [Filled] flag has been set");
  143. deliveryitems = updates;
  144. }
  145. private void ClearDeliveryItems(Requisition entity, ref IList<DeliveryItem> deliveryitems)
  146. {
  147. if (!LoadDeliveryItems(entity, ref deliveryitems))
  148. {
  149. Log("ClearDeliveryItems() - no delivery items to update");
  150. return;
  151. }
  152. if (deliveryitems.Any())
  153. FindSubStore<DeliveryItem>().Delete(deliveryitems, "Requisition [Filled] flag has been cleared");
  154. deliveryitems = new List<DeliveryItem>();
  155. }
  156. private void UpdateDeliveryItems(Requisition entity, ref IList<RequisitionItem> requisitionitems,
  157. ref IList<DeliveryItem> deliveryitems)
  158. {
  159. Log("UpdateDeliveryItems() - starting");
  160. if (!NeedsUpdating(entity, x => x.Filled))
  161. {
  162. Log("UpdateDeliveryItems() - NeedsUpdate() return false");
  163. return;
  164. }
  165. var oldfilled = entity.GetOriginalValue(x => x.Filled);
  166. var newfilled = entity.Filled;
  167. // Gone from Blank to Filled -> Create a Batch
  168. if (oldfilled.IsEmpty() && !newfilled.IsEmpty())
  169. {
  170. Log("UpdateDeliveryItems() - Filled has been set");
  171. ClearDeliveryItems(entity, ref deliveryitems);
  172. CreateDeliveryItems(entity, ref requisitionitems, ref deliveryitems);
  173. }
  174. // Gone from Filled to Blank -> Clear Out the Batch
  175. else if (newfilled.IsEmpty() && !oldfilled.IsEmpty())
  176. {
  177. Log("UpdateDeliveryItems() - Filled has been cleared");
  178. ClearDeliveryItems(entity, ref deliveryitems);
  179. }
  180. // Do nothing - filled flag has been updated, not set or cleared
  181. Log("UpdateDeliveryItems() - done");
  182. }
  183. #endregion
  184. #region StockMovements
  185. private StockMovement CreateStockMovement(Guid employeeid, DateTime date, Guid batchid, Guid productid, Guid locationid,
  186. Guid styleid, Guid jobid, IDimensions dimensions, Guid txnid, bool system, string note)
  187. {
  188. var movement = new StockMovement();
  189. movement.Batch.ID = batchid;
  190. movement.Product.ID = productid;
  191. movement.Location.ID = locationid;
  192. movement.Style.ID = styleid;
  193. movement.Job.ID = jobid;
  194. movement.Dimensions.CopyFrom(dimensions);
  195. movement.System = system;
  196. movement.Transaction = txnid;
  197. movement.Notes = note;
  198. movement.Date = date;
  199. movement.Employee.ID = employeeid;
  200. return movement;
  201. }
  202. private void CreateStockBatch(Requisition entity, ref IList<RequisitionItem> items)
  203. {
  204. if(!LoadRequisitionItems(entity, ref items))
  205. {
  206. Log("CreateStockBatch() - no items to update!");
  207. return;
  208. }
  209. var batch = new StockMovementBatch
  210. {
  211. Type = StockMovementBatchType.Issue,
  212. TimeStamp = entity.Filled,
  213. Notes = string.Format("Requisition #{0}", entity.Number)
  214. };
  215. batch.Employee.ID = entity.Employee.ID;
  216. batch.Requisition.ID = entity.ID;
  217. FindSubStore<StockMovementBatch>().Save(batch, "");
  218. var updates = new List<StockMovement>();
  219. foreach (var item in items)
  220. {
  221. var locationid = item.Location.ID;
  222. var productid = item.Product.ID;
  223. var styleid = item.Style.ID;
  224. var unitsize = item.Dimensions.UnitSize;
  225. var jobid = entity.JobLink.ID;
  226. var holdingQty = 0.0;
  227. if(jobid != Guid.Empty)
  228. {
  229. var holdings = Provider.Query<StockHolding>(
  230. new Filter<StockHolding>(x => x.Location.ID).IsEqualTo(locationid)
  231. .And(x => x.Product.ID).IsEqualTo(productid)
  232. .And(x => x.Style.ID).IsEqualTo(styleid)
  233. .And(x => x.Dimensions).DimensionEquals(item.Dimensions)
  234. .And(x => x.Job.ID).IsEqualTo(jobid)
  235. .And(x => x.Qty).IsGreaterThan(0.0),
  236. new Columns<StockHolding>(x => x.Qty)
  237. );
  238. holdingQty = holdings.Rows.FirstOrDefault()?.Get<StockHolding, double>(x => x.Qty) ?? 0.0;
  239. }
  240. var qty = item.ActualQuantity;
  241. var dimensions = item.Product.DefaultInstance.Dimensions;
  242. var txnid = Guid.NewGuid();
  243. if(holdingQty < qty)
  244. {
  245. // Don't pull more than the required quantity, meaning if the holding is negative, it will remain negative.
  246. var extraRequired = Math.Max(qty - holdingQty, qty);
  247. // We don't have enough stock in this case, so transfer the necessary stock from general. We don't check for quantity in general stock,
  248. // but instead will let general stock go negative if not enough. Obviously we have the stock, because its being sent to site. So if we do
  249. // get negatives, it means probably our number are wrong I think.
  250. // Transfer the necessary balance from General Stock...
  251. var from = CreateStockMovement(entity.Employee.ID, entity.Filled, batch.ID, productid, locationid, styleid, Guid.Empty,
  252. dimensions, txnid, true, string.Format("Requisition #{0} Internal Transfer", entity.Number));
  253. from.Issued = extraRequired;
  254. from.Type = StockMovementType.TransferOut;
  255. // ... to the job.
  256. var to = CreateStockMovement(entity.Employee.ID, entity.Filled, batch.ID, productid, locationid, styleid, jobid, dimensions, txnid, true,
  257. string.Format("Requisition #{0} Internal Transfer", entity.Number));
  258. to.Received = extraRequired;
  259. to.Type = StockMovementType.TransferIn;
  260. updates.Add(from);
  261. updates.Add(to);
  262. // Now we have a full qty in the job holding, and we can issue to site.
  263. }
  264. var mvt = CreateStockMovement(entity.Employee.ID, entity.Filled, batch.ID, productid, locationid, styleid, jobid, dimensions, txnid,
  265. false,
  266. string.Format("Requisition #{0}", entity.Number));
  267. mvt.Issued = qty;
  268. mvt.Type = StockMovementType.Issue;
  269. updates.Add(mvt);
  270. }
  271. FindSubStore<StockMovement>().Save(updates, "");
  272. }
  273. private void ClearStockBatch(Requisition entity)
  274. {
  275. Log("ClearStockBatch()");
  276. var batches = Provider.Query(
  277. new Filter<StockMovementBatch>(x => x.Requisition.ID).IsEqualTo(entity.ID),
  278. new Columns<StockMovementBatch>(x => x.ID)
  279. ).Rows.Select(x => x.ToObject<StockMovementBatch>());
  280. if (batches.Any())
  281. FindSubStore<StockMovementBatch>().Delete(batches, "");
  282. }
  283. private void UpdateStockBatches(Requisition entity, ref IList<RequisitionItem> items)
  284. {
  285. Log("UpdateStockBatch() - starting");
  286. if (!NeedsUpdating(entity, x => x.StockUpdated))
  287. return;
  288. var oldupdate = entity.GetOriginalValue(x => x.StockUpdated);
  289. var newupdate = entity.StockUpdated;
  290. // Gone from Blank to Updated -> Create a Batch
  291. if (oldupdate.IsEmpty() && !newupdate.IsEmpty())
  292. {
  293. Log("UpdateStockBatch() - creating batch");
  294. ClearStockBatch(entity);
  295. CreateStockBatch(entity, ref items);
  296. }
  297. // Gone from Updated to Blank -> Clear Out the Batch
  298. else if (newupdate.IsEmpty() && !oldupdate.IsEmpty())
  299. {
  300. Log("UpdateStockBatch() - clearing batch");
  301. ClearStockBatch(entity);
  302. }
  303. // Do nothing - Updated flag has been updated, not set or cleared
  304. Log("UpdateStockBatch() - done");
  305. }
  306. #endregion
  307. }
  308. }