RequisitionStore.cs 16 KB

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