RequisitionStore.cs 15 KB

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