JobRequisitionItem.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using Comal.Classes.SecurityDescriptors;
  7. using InABox.Clients;
  8. using InABox.Core;
  9. using PRSClasses;
  10. namespace Comal.Classes
  11. {
  12. public enum JobRequisitionItemStatus
  13. {
  14. Stock,
  15. Treatment,
  16. Allocated,
  17. Issued,
  18. Cancelled,
  19. Archived
  20. }
  21. public interface IJobRequisitionItem : IEntity
  22. {
  23. public DateTime Cancelled { get; set; }
  24. }
  25. [Caption("Items")]
  26. [UserTracking(typeof(Job))]
  27. public class JobRequisitionItem : StockEntity, IRemotable, IPersistent, IOneToMany<JobRequisition>,
  28. ILicense<ProjectManagementLicense>, IJobMaterial, ISequenceable, IJobRequisitionItem, IProblems<ManagedProblem>
  29. {
  30. [EntityRelationship(DeleteAction.Cascade)]
  31. [Editable(Editable.Hidden)]
  32. public JobLink Job { get; set; }
  33. [EntityRelationship(DeleteAction.Cascade)]
  34. [Editable(Editable.Hidden)]
  35. public JobRequisitionLink Requisition { get; set; }
  36. [EntityRelationship(DeleteAction.SetNull)]
  37. [EditorSequence(1)]
  38. [RequiredColumn]
  39. public override ProductLink Product { get; set; }
  40. [EditorSequence(2)]
  41. [RequiredColumn]
  42. public ProductStyleLink Style { get; set; }
  43. [EditorSequence(3)]
  44. [RequiredColumn]
  45. [DimensionsEditor(typeof(StockDimensions))]
  46. public override StockDimensions Dimensions { get; set; }
  47. [EditorSequence(4)]
  48. [RequiredColumn]
  49. public decimal Qty { get; set; }
  50. private class TotalQtyFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  51. {
  52. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  53. Formula(FormulaOperator.Multiply, Property(x => x.Qty), Property(x => x.Dimensions.Value));
  54. }
  55. [DecimalEditor(Editable = Editable.Hidden)]
  56. [ComplexFormula(typeof(TotalQtyFormula))]
  57. public decimal TotalQty { get; set; }
  58. [EditorSequence(5)]
  59. public decimal UnitCost { get; set; }
  60. [EditorSequence(6)]
  61. [CurrencyEditor(Summary = Summary.Sum)]
  62. public decimal TotalCost { get; set; }
  63. [EditorSequence(7)]
  64. [MemoEditor]
  65. public string Notes { get; set; }
  66. [EditorSequence(8)]
  67. public SupplierLink Supplier { get; set; }
  68. private class StatusFormula : ComplexFormulaGenerator<JobRequisitionItem, JobRequisitionItemStatus>
  69. {
  70. public override IComplexFormulaNode<JobRequisitionItem, JobRequisitionItemStatus> GetFormula() =>
  71. If<JobRequisitionItem, DateTime, JobRequisitionItemStatus>(
  72. Property<JobRequisitionItem,DateTime>(x => x.Cancelled),
  73. Condition.NotEqual,
  74. Constant<JobRequisitionItem,DateTime>(DateTime.MinValue)
  75. ).Then(
  76. Constant(JobRequisitionItemStatus.Cancelled)
  77. ).Else(
  78. If<JobRequisitionItem, DateTime, JobRequisitionItemStatus>(
  79. Property<JobRequisitionItem,DateTime>(x => x.Archived),
  80. Condition.NotEqual,
  81. Constant<JobRequisitionItem,DateTime>(DateTime.MinValue)
  82. ).Then(
  83. Constant(JobRequisitionItemStatus.Archived)
  84. ).Else(
  85. If<JobRequisitionItem, decimal, JobRequisitionItemStatus>(
  86. Property<JobRequisitionItem,decimal>(x => x.Issued),
  87. Condition.GreaterThanOrEqualTo,
  88. Property<JobRequisitionItem,decimal>(x => x.Qty)
  89. ).Then(
  90. Constant(JobRequisitionItemStatus.Issued)
  91. ).Else(
  92. If<JobRequisitionItem, decimal, JobRequisitionItemStatus>(
  93. Formula(
  94. FormulaOperator.Add,
  95. Property<JobRequisitionItem,decimal>(x => x.Issued),
  96. Property<JobRequisitionItem,decimal>(x => x.Allocated)
  97. ),
  98. Condition.GreaterThanOrEqualTo,
  99. Property<JobRequisitionItem,decimal>(x => x.Qty)
  100. ).Then(
  101. Constant(JobRequisitionItemStatus.Allocated)
  102. ).Else(
  103. If<JobRequisitionItem, decimal, JobRequisitionItemStatus>(
  104. Formula(
  105. FormulaOperator.Add,
  106. Property<JobRequisitionItem,decimal>(x => x.InStock),
  107. Property<JobRequisitionItem,decimal>(x => x.Issued)
  108. ),
  109. Condition.GreaterThanOrEqualTo,
  110. Property<JobRequisitionItem,decimal>(x => x.Qty)
  111. ).Then(
  112. Constant(JobRequisitionItemStatus.Treatment)
  113. ).Else(
  114. Constant(JobRequisitionItemStatus.Stock)
  115. )
  116. )
  117. )
  118. )
  119. );
  120. }
  121. [ComplexFormula(typeof(StatusFormula))]
  122. [EditorSequence(8)]
  123. public JobRequisitionItemStatus Status { get; set; }
  124. private class InStockFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  125. {
  126. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  127. If<JobRequisitionItem, string, decimal>(
  128. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  129. Condition.Equals,
  130. Constant<JobRequisitionItem, string>("")
  131. ).Then(
  132. Aggregate<StockMovement>(AggregateCalculation.Sum, x => x.Property(x => x.Units))
  133. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  134. ).Else(
  135. Formula(FormulaOperator.Divide,
  136. Aggregate<StockMovement>(AggregateCalculation.Sum, x => x.Property(x => x.Qty))
  137. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID),
  138. Property(x => x.Dimensions.Value)
  139. )
  140. );
  141. }
  142. /// <summary>
  143. /// The amount of this requisition item that is currently in stock, which is an aggregate of the <see cref="StockMovement.Units"/> property.
  144. /// </summary>
  145. [ComplexFormula(typeof(InStockFormula))]
  146. [DecimalEditor(Editable = Editable.Disabled)]
  147. [EditorSequence(10)]
  148. public decimal InStock { get; set; }
  149. private class OnOrderFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  150. {
  151. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  152. If<JobRequisitionItem, string, decimal>(
  153. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  154. Condition.Equals,
  155. Constant<JobRequisitionItem, string>("")
  156. ).Then(
  157. Aggregate(
  158. AggregateCalculation.Sum,
  159. x => x.Property(x => x.Quantity),
  160. Filter<PurchaseOrderItemAllocation>.Where(x => x.Item.ReceivedDate).IsEqualTo(null))
  161. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  162. .WithLink(x => x.Item.Product.ID, x => x.Product.ID)
  163. ).Else(
  164. Formula(FormulaOperator.Divide,
  165. Aggregate(
  166. AggregateCalculation.Sum,
  167. x => x.Property(x => x.Quantity),
  168. Filter<PurchaseOrderItemAllocation>.Where(x => x.Item.ReceivedDate).IsEqualTo(null))
  169. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  170. .WithLink(x => x.Item.Product.ID, x => x.Product.ID),
  171. Property(x => x.Dimensions.Value)
  172. )
  173. );
  174. }
  175. [ComplexFormula(typeof(OnOrderFormula))]
  176. [DecimalEditor(Editable = Editable.Disabled)]
  177. [EditorSequence(11)]
  178. public decimal OnOrder { get; set; }
  179. private class GeneralOrderFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  180. {
  181. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  182. Formula(
  183. FormulaOperator.Add,
  184. Aggregate<PurchaseOrderItem>(
  185. AggregateCalculation.Sum,
  186. x => x.Property(x => x.Unallocated),
  187. Filter<PurchaseOrderItem>.Where(x => x.ReceivedDate).IsEqualTo(null)
  188. .And(x => x.Job.ID).IsEqualTo(Guid.Empty)
  189. )
  190. .WithLink(x => x.Product.ID, x => x.Product.ID),
  191. If<JobRequisitionItem, string, decimal>(
  192. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  193. Condition.Equals,
  194. Constant<JobRequisitionItem, string>("")
  195. )
  196. .Then(
  197. Aggregate(
  198. AggregateCalculation.Sum,
  199. x => x.Property(c => c.Quantity),
  200. Filter<PurchaseOrderItemAllocation>.Where(x => x.Item.ReceivedDate).IsEqualTo(null)
  201. .And(x => x.Job.ID).IsEqualTo(Guid.Empty)
  202. .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty)
  203. ).WithLink(x => x.Item.Product.ID, x => x.Product.ID)
  204. )
  205. .Else(
  206. Formula(
  207. FormulaOperator.Divide,
  208. Aggregate(
  209. AggregateCalculation.Sum,
  210. x => x.Property(c => c.Quantity),
  211. Filter<PurchaseOrderItemAllocation>.Where(x => x.Item.ReceivedDate).IsEqualTo(null)
  212. .And(x => x.Job.ID).IsEqualTo(Guid.Empty)
  213. .And(x => x.JobRequisitionItem.ID).IsEqualTo(Guid.Empty)
  214. ).WithLink(x => x.Item.Product.ID, x => x.Product.ID),
  215. Property(x => x.Dimensions.Value)
  216. )
  217. )
  218. );
  219. }
  220. [ComplexFormula(typeof(GeneralOrderFormula))]
  221. [DecimalEditor(Editable = Editable.Hidden)]
  222. public decimal GeneralOrder { get; set; }
  223. private class TotalOrdersFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  224. {
  225. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  226. If<JobRequisitionItem, string, decimal>(
  227. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  228. Condition.Equals,
  229. Constant<JobRequisitionItem, string>("")
  230. ).Then(
  231. Aggregate(
  232. AggregateCalculation.Sum,
  233. x => x.Property(x => x.Quantity),
  234. Filter<PurchaseOrderItemAllocation>.Where(x => x.Item.ReceivedDate).IsEqualTo(null))
  235. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  236. ).Else(
  237. Formula(FormulaOperator.Divide,
  238. Aggregate(
  239. AggregateCalculation.Sum,
  240. x => x.Property(x => x.Quantity),
  241. Filter<PurchaseOrderItemAllocation>.Where(x => x.Item.ReceivedDate).IsEqualTo(null))
  242. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID),
  243. Property(x => x.Dimensions.Value)
  244. )
  245. );
  246. }
  247. [ComplexFormula(typeof(TotalOrdersFormula))]
  248. [DecimalEditor(Editable = Editable.Disabled, Visible = Visible.Optional)]
  249. [EditorSequence(12)]
  250. public decimal TotalOrders { get; set; }
  251. private class TreatmentRequiredFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  252. {
  253. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  254. Formula(
  255. FormulaOperator.Subtract,
  256. Property(x => x.InStock),
  257. Property(x => x.Allocated),
  258. Formula(
  259. FormulaOperator.Subtract,
  260. Property(x => x.TotalOrders),
  261. Property(x => x.OnOrder)
  262. )
  263. );
  264. }
  265. [ComplexFormula(typeof(TreatmentRequiredFormula))]
  266. [DecimalEditor(Editable = Editable.Disabled)]
  267. [EditorSequence(13)]
  268. public decimal TreatmentRequired { get; set; }
  269. private class TreatmentOnOrderFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  270. {
  271. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  272. Formula(FormulaOperator.Subtract, Property(x => x.TotalOrders), Property(x => x.OnOrder));
  273. }
  274. [ComplexFormula(typeof(TreatmentOnOrderFormula))]
  275. [DecimalEditor(Editable = Editable.Disabled)]
  276. [EditorSequence(14)]
  277. public decimal TreatmentOnOrder { get; set; }
  278. private class AllocatedFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  279. {
  280. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  281. Formula(
  282. FormulaOperator.Subtract,
  283. Formula(
  284. FormulaOperator.Add,
  285. If<JobRequisitionItem, string, decimal>(
  286. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  287. Condition.Equals,
  288. Constant<JobRequisitionItem, string>("")
  289. ).Then(
  290. Aggregate<StockMovement>(
  291. AggregateCalculation.Sum,
  292. x => x.Property(x => x.Units))
  293. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  294. .WithLink(x => x.Style.ID, x => x.Style.ID)
  295. ).Else(
  296. Formula(FormulaOperator.Divide,
  297. Aggregate<StockMovement>(
  298. AggregateCalculation.Sum,
  299. x => x.Property(x => x.Qty))
  300. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  301. .WithLink(x => x.Style.ID, x => x.Style.ID),
  302. Property(x => x.Dimensions.Value)
  303. )
  304. ),
  305. Property(x => x.OnOrder)
  306. ),
  307. Property(x => x.TotalOrders)
  308. );
  309. }
  310. [ComplexFormula(typeof(AllocatedFormula))]
  311. [DecimalEditor(Editable = Editable.Disabled)]
  312. [EditorSequence(15)]
  313. public decimal Allocated { get; set; }
  314. private class PickRequestedFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  315. {
  316. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  317. If<JobRequisitionItem, string, decimal>(
  318. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  319. Condition.Equals,
  320. Constant<JobRequisitionItem, string>("")
  321. ).Then(
  322. Aggregate<PickingListItem>(
  323. AggregateCalculation.Sum,
  324. x => x.Property(x => x.Quantity),
  325. Filter<PickingListItem>.Where(x => x.PickingList.StockUpdated).IsEqualTo(
  326. DateTime.MinValue)
  327. )
  328. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  329. ).Else(
  330. Formula(FormulaOperator.Divide,
  331. Aggregate<PickingListItem>(
  332. AggregateCalculation.Sum,
  333. x => x.Property(x => x.Quantity),
  334. Filter<PickingListItem>.Where(x => x.PickingList.StockUpdated).IsEqualTo(
  335. DateTime.MinValue)
  336. )
  337. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID),
  338. Property(x => x.Dimensions.Value)
  339. )
  340. );
  341. }
  342. [ComplexFormula(typeof(PickRequestedFormula))]
  343. [DecimalEditor(Editable = Editable.Disabled)]
  344. [EditorSequence(16)]
  345. public decimal PickRequested { get; set; }
  346. private class IssuedFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  347. {
  348. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  349. If<JobRequisitionItem, string, decimal>(
  350. Property<JobRequisitionItem, string>(x => x.Dimensions.Unit.Conversion),
  351. Condition.Equals,
  352. Constant<JobRequisitionItem, string>("")
  353. ).Then(
  354. Aggregate<StockMovement>(
  355. AggregateCalculation.Sum,
  356. x => x.Property(x => x.Issued),
  357. Filter<StockMovement>.Where(x => x.Type).IsEqualTo(StockMovementType.Issue))
  358. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  359. .WithLink(x => x.Style.ID, x => x.Style.ID)
  360. ).Else(
  361. Formula(FormulaOperator.Divide,
  362. Aggregate<StockMovement>(
  363. AggregateCalculation.Sum,
  364. x => x.Property(x => x.Qty), // Using Qty here; Issued and Units should be the same
  365. Filter<StockMovement>.Where(x => x.Type).IsEqualTo(StockMovementType.Issue))
  366. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID)
  367. .WithLink(x => x.Style.ID, x => x.Style.ID),
  368. Property(x => x.Dimensions.Value)
  369. )
  370. );
  371. }
  372. [ComplexFormula(typeof(IssuedFormula))]
  373. [DecimalEditor(Editable = Editable.Disabled)]
  374. [EditorSequence(17)]
  375. public decimal Issued { get; set; }
  376. private class TotalIssuedFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  377. {
  378. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  379. Formula(FormulaOperator.Multiply, Property(x => x.Issued), Property(x => x.Dimensions.Value));
  380. }
  381. [DecimalEditor(Editable = Editable.Hidden)]
  382. [ComplexFormula(typeof(TotalIssuedFormula))]
  383. public decimal TotalIssued { get; set; }
  384. private class TotalAllocatedFormula : ComplexFormulaGenerator<JobRequisitionItem, decimal>
  385. {
  386. public override IComplexFormulaNode<JobRequisitionItem, decimal> GetFormula() =>
  387. Formula(FormulaOperator.Multiply, Property(x => x.Allocated), Property(x => x.Dimensions.Value));
  388. }
  389. [DecimalEditor(Editable = Editable.Hidden)]
  390. [ComplexFormula(typeof(TotalAllocatedFormula))]
  391. public decimal TotalAllocated { get; set; }
  392. private class PurchaseOrderNumbersFormula : ComplexFormulaGenerator<JobRequisitionItem, string>
  393. {
  394. public override IComplexFormulaNode<JobRequisitionItem, string> GetFormula() =>
  395. Aggregate<PurchaseOrderItemAllocation>(
  396. AggregateCalculation.Concat,
  397. x => x.Property(x => x.Item.PurchaseOrder.PONumber))
  398. .WithLink(x => x.JobRequisitionItem.ID, x => x.ID);
  399. }
  400. [ComplexFormula(typeof(PurchaseOrderNumbersFormula))]
  401. [TextBoxEditor(Editable = Editable.Hidden)]
  402. public string PurchaseOrderNumbers { get; set; }
  403. [RequiredColumn]
  404. [EditorSequence(18)]
  405. public DateTime Cancelled { get; set; } = DateTime.MinValue;
  406. [RequiredColumn]
  407. [EditorSequence(19)]
  408. public DateTime Archived { get; set; } = DateTime.MinValue;
  409. [RequiredColumn]
  410. [EditorSequence(20)]
  411. public DateTime OrderRequired { get; set; } = DateTime.MinValue;
  412. [EditorSequence("Issues", 1)]
  413. public ManagedProblem Problem { get; set; }
  414. [NullEditor]
  415. public long Sequence { get; set; }
  416. static JobRequisitionItem()
  417. {
  418. LinkedProperties.Register<JobRequisitionItem, ProductStyleLink, Guid>(x => x.Product.DefaultInstance.Style, x => x.ID, x => x.Style.ID);
  419. LinkedProperties.Register<JobRequisitionItem, ProductStyleLink, String>(x => x.Product.DefaultInstance.Style, x => x.Code, x => x.Style.Code);
  420. LinkedProperties.Register<JobRequisitionItem, ProductStyleLink, String>(x => x.Product.DefaultInstance.Style, x => x.Description, x => x.Style.Description);
  421. LinkedProperties.Register<JobRequisitionItem, ProductInstanceLink, decimal>(x => x.Product.DefaultInstance, x => x.NettCost,
  422. x => x.UnitCost);
  423. StockEntity.LinkStockDimensions<JobRequisitionItem>();
  424. }
  425. private bool bChanging;
  426. protected override void DoPropertyChanged(string name, object? before, object? after)
  427. {
  428. if (bChanging)
  429. return;
  430. try
  431. {
  432. bChanging = true;
  433. if (name.Equals(nameof(Qty)) && after is decimal qty)
  434. TotalCost = UnitCost * qty;
  435. else if (name.Equals(nameof(UnitCost)) && after is decimal cost)
  436. TotalCost = cost * Qty;
  437. else if (name.Equals(nameof(TotalCost)) && after is decimal total)
  438. {
  439. if (Qty == 0)
  440. Qty = 1;
  441. UnitCost = total / Qty;
  442. }
  443. }
  444. finally
  445. {
  446. bChanging = false;
  447. }
  448. base.DoPropertyChanged(name, before, after);
  449. }
  450. public class JRICostData
  451. {
  452. public Guid[] ProductIDs { get; set; } = Array.Empty<Guid>();
  453. public ProductInstance[] Instances { get; set; } = Array.Empty<ProductInstance>();
  454. public SupplierProduct[] SupplierProducts { get; set; } = Array.Empty<SupplierProduct>();
  455. public SupplierDiscount[] SupplierDiscounts { get; set; } = Array.Empty<SupplierDiscount>();
  456. }
  457. public static void UpdateCosts(IEnumerable<JobRequisitionItem> items, Dictionary<String,object?> changes, JRICostData? data = null)
  458. {
  459. void UpdateValue<TType>(JobRequisitionItem item,Expression<Func<JobRequisitionItem, TType>> property, TType value)
  460. {
  461. CoreUtils.MonitorChanges(
  462. item,
  463. () => CoreUtils.SetPropertyValue(item, CoreUtils.GetFullPropertyName(property, "."), value),
  464. changes
  465. );
  466. }
  467. var _items = items as JobRequisitionItem[] ?? items.ToArray();
  468. var productids = _items.Where(x => x.Product.ID != Guid.Empty).Select(x => x.Product.ID).ToArray();
  469. if(productids.Length == 0)
  470. return;
  471. var jobids = _items.Where(x => x.Product.ID != Guid.Empty).Select(x => x.Job.ID).ToList().Append(Guid.Empty).Distinct().ToArray();
  472. var supplierIDs = _items.Where(x => x.Supplier.ID != Guid.Empty).Select(x => x.Supplier.ID).ToArray();
  473. var needProducts = data is null || productids.Any(x => !data.ProductIDs.Contains(x));
  474. SupplierProduct[] supplierProducts;
  475. SupplierDiscount[] supplierDiscounts;
  476. ProductInstance[] productInstances;
  477. if(needProducts)
  478. {
  479. MultiQuery query = new MultiQuery();
  480. query.Add(
  481. Filter<SupplierProduct>.Where(x => x.Product.ID).InList(productids).And(x => x.Supplier.ID)
  482. .InList(supplierIDs),
  483. Columns.None<SupplierProduct>().Add(x => x.Product.ID)
  484. .Add(x => x.Supplier.ID)
  485. .Add(x => x.Style.ID)
  486. .AddDimensionsColumns(x => x.Dimensions, Classes.Dimensions.ColumnsType.Local)
  487. .Add(x => x.Job.ID)
  488. .Add(x => x.SupplierCode)
  489. .Add(x => x.SupplierDescription)
  490. .Add(x=>x.TradePrice)
  491. .Add(x => x.CostPrice)
  492. .Add(x => x.DiscountGroup.ID)
  493. );
  494. query.Add(
  495. Filter<SupplierDiscount>.Where(x => x.Job.ID).InList(jobids)
  496. .And(x => x.Group.ID).InQuery(
  497. Filter<SupplierProduct>.Where(x => x.Product.ID).InList(productids),
  498. x => x.DiscountGroup.ID
  499. )
  500. , Columns.None<SupplierDiscount>()
  501. .Add(x => x.Group.ID)
  502. .Add(x => x.Job.ID)
  503. .Add(x=>x.Group.Type)
  504. .Add(x => x.Value)
  505. );
  506. if(needProducts)
  507. {
  508. query.Add(
  509. Filter<ProductInstance>.Where(x =>x.Product.ID).InList(productids),
  510. Columns.None<ProductInstance>().Add(x=>x.Product.ID)
  511. .Add(x => x.Style.ID)
  512. .AddDimensionsColumns(x => x.Dimensions, Classes.Dimensions.ColumnsType.Local)
  513. .Add(x => x.NettCost)
  514. );
  515. }
  516. query.Query();
  517. supplierProducts = query.Get<SupplierProduct>().ToArray<SupplierProduct>();
  518. supplierDiscounts = query.Get<SupplierDiscount>().ToArray<SupplierDiscount>();
  519. productInstances = needProducts
  520. ? query.Get<ProductInstance>().ToArray<ProductInstance>()
  521. : data!.Instances;
  522. if(data != null)
  523. {
  524. data.SupplierProducts = supplierProducts;
  525. data.SupplierDiscounts = supplierDiscounts;
  526. if (needProducts)
  527. {
  528. data.ProductIDs = productids;
  529. data.Instances = productInstances;
  530. }
  531. }
  532. }
  533. else
  534. {
  535. supplierProducts = data!.SupplierProducts;
  536. supplierDiscounts = data!.SupplierDiscounts;
  537. productInstances = data!.Instances;
  538. }
  539. foreach (var item in items)
  540. {
  541. if(supplierProducts != null)
  542. {
  543. //Check Supplier / Job Specific Pricing
  544. var supplierProduct = supplierProducts.FirstOrDefault(x =>
  545. x.Product.ID == item.Product.ID
  546. && x.Supplier.ID == item.Supplier.ID
  547. && x.Style.ID == item.Style.ID
  548. && x.Dimensions.Equals(item.Dimensions)
  549. && x.Job.ID == item.Job.ID);
  550. if (supplierProduct != null)
  551. {
  552. UpdateValue<decimal>(item, x => x.UnitCost, supplierProduct.CostPrice);
  553. continue;
  554. }
  555. // Check Supplier Pricing
  556. supplierProduct = supplierProducts.FirstOrDefault(x =>
  557. x.Product.ID == item.Product.ID
  558. && x.Supplier.ID == item.Supplier.ID
  559. && x.Style.ID == item.Style.ID
  560. && x.Dimensions.Equals(item.Dimensions)
  561. && x.Job.ID == Guid.Empty);
  562. if (supplierProduct != null)
  563. {
  564. var discount = supplierDiscounts.FirstOrDefault(x=>x.Job.ID == item.Job.ID && x.Group.ID == supplierProduct.DiscountGroup.ID);
  565. if (discount == null)
  566. discount = supplierDiscounts.FirstOrDefault(x=>x.Job.ID == Guid.Empty && x.Group.ID == supplierProduct.DiscountGroup.ID);
  567. if (discount != null)
  568. {
  569. if (discount.Group.Type == SupplierDiscountGroupType.Discount)
  570. {
  571. var disc = (100 - discount?.Value ?? 0) / 100;
  572. UpdateValue<decimal>(item, x => x.UnitCost, supplierProduct.TradePrice * disc);
  573. }
  574. else
  575. UpdateValue<decimal>(item, x => x.UnitCost, discount.Value);
  576. }
  577. else
  578. UpdateValue<decimal>(item, x => x.UnitCost, supplierProduct.CostPrice);
  579. continue;
  580. }
  581. }
  582. // Check Specific Product Instance
  583. var productInstance = productInstances.FirstOrDefault(x =>
  584. x.Product.ID == item.Product.ID
  585. && x.Style.ID == item.Style.ID
  586. && x.Dimensions.Equals(item.Dimensions));
  587. if (productInstance != null)
  588. UpdateValue<decimal>(item, x => x.UnitCost, productInstance.NettCost);
  589. else
  590. UpdateValue<decimal>(item, x => x.UnitCost, 0);
  591. }
  592. }
  593. }
  594. }