JobRequisitionItem.cs 30 KB

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