ProgressClaimGrid.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. using InABox.DynamicGrid;
  5. using PRSDesktop.Panels.Invoices;
  6. using Syncfusion.Windows.Shared;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using System.Windows;
  14. using System.Windows.Controls;
  15. using System.Windows.Forms;
  16. using System.Windows.Media;
  17. namespace PRSDesktop;
  18. public class ProgressClaim : BaseObject
  19. {
  20. public JobScopeLink JobScope { get; set; }
  21. [CurrencyEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  22. public double Materials { get; set; }
  23. [CurrencyEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
  24. public double Labour { get; set; }
  25. [DoubleEditor(Editable = Editable.Disabled,Summary = Summary.Sum)]
  26. public double PreviouslyClaimed { get; set; }
  27. [DoubleEditor(Editable = Editable.Disabled)]
  28. public double PreviouslyClaimedPercent { get; set; }
  29. [DoubleEditor(Editable = Editable.Enabled)]
  30. public double PercentCost { get; set; }
  31. [DoubleEditor(Editable = Editable.Disabled,Summary = Summary.Sum)]
  32. public double Cost { get; set; }
  33. [NullEditor]
  34. public Guid GLCodeID { get; set; }
  35. }
  36. public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
  37. {
  38. private DynamicGridCustomColumnsComponent<ProgressClaim> ColumnsComponent;
  39. public Guid JobID { get; set; }
  40. public Guid InvoiceID { get; set; }
  41. public double ProjectContract { get; private set; }
  42. public double ProjectVariations { get; private set; }
  43. public double ProjectSubTotal { get; private set; }
  44. public double ProjectRetention { get; private set; }
  45. public double ProjectRetentionPercent { get; private set; }
  46. public double ProjectTotal { get; private set; }
  47. public double CompletedContract { get; private set; }
  48. public double CompletedContractPercent { get; private set; }
  49. public double CompletedVariations { get; private set; }
  50. public double CompletedVariationsPercent { get; private set; }
  51. public double CompletedSubTotal { get; private set; }
  52. public double CompletedSubTotalPercent { get; private set; }
  53. public double CompletedRetention { get; private set; }
  54. public double CompletedRetentionPercent { get; private set; }
  55. public double CompletedTotal { get; private set; }
  56. public double CompletedTotalPercent { get; private set; }
  57. public double PreviousContract { get; private set; }
  58. public double PreviousContractPercent { get; private set; }
  59. public double PreviousVariations { get; private set; }
  60. public double PreviousVariationsPercent { get; private set; }
  61. public double PreviousSubTotal { get; private set; }
  62. public double PreviousSubTotalPercent { get; private set; }
  63. public double PreviousRetention { get; private set; }
  64. public double PreviousRetentionPercent { get; private set; }
  65. public double PreviousTotal { get; private set; }
  66. public double PreviousTotalPercent { get; private set; }
  67. public double CurrentContract { get; private set; }
  68. public double CurrentContractPercent { get; private set; }
  69. public double CurrentVariations { get; private set; }
  70. public double CurrentVariationsPercent { get; private set; }
  71. public double CurrentSubTotal { get; private set; }
  72. public double CurrentSubTotalPercent { get; private set; }
  73. public double CurrentRetention { get; private set; }
  74. public double CurrentRetentionPercent { get; private set; }
  75. public double CurrentTotal { get; private set; }
  76. public double CurrentTotalPercent { get; private set; }
  77. protected override void Init()
  78. {
  79. base.Init();
  80. ColumnsComponent = new(this, nameof(ProgressClaimGrid));
  81. }
  82. protected override void DoReconfigure(DynamicGridOptions options)
  83. {
  84. base.DoReconfigure(options);
  85. options.SelectColumns = false;
  86. }
  87. private Job Job;
  88. private JobScope[] Scopes;
  89. private Invoice[] Invoices;
  90. private Invoice Invoice;
  91. private List<InvoiceLine> InvoiceLines;
  92. private bool _loadedData = false;
  93. private void LoadData()
  94. {
  95. var columns =
  96. Columns.None<JobScope>()
  97. .Add(x => x.ID)
  98. .Add(x => x.Number)
  99. .Add(x => x.ExTax)
  100. .Add(x => x.InvoiceExTax)
  101. .Add(x => x.TaxCode.ID)
  102. .Add(x => x.TaxCode.Rate)
  103. .Add(x => x.Tax)
  104. .Add(x => x.Description)
  105. .Add(x => x.MaterialsExTax)
  106. .Add(x => x.Job.DefaultScope.ID)
  107. .Add(x => x.Type);
  108. var scopeColumn = new Column<ProgressClaim>(x => x.JobScope).Property + ".";
  109. foreach(var column in DataColumns().Where(x => x.Property.StartsWith(scopeColumn)))
  110. {
  111. columns.Add(column.Property[scopeColumn.Length..]);
  112. }
  113. MultiQuery query = new MultiQuery();
  114. query.Add(
  115. new Filter<Job>(x=>x.ID).IsEqualTo(JobID),
  116. Columns.None<Job>()
  117. .Add(x => x.ID)
  118. .Add(x=>x.Retention.Maximum)
  119. .Add(x=>x.Retention.Rate)
  120. .Add(x=>x.Retention.IncludeVariations)
  121. .Add(x=>x.Account.ID)
  122. .Add(x=>x.Account.GLCode.ID)
  123. .Add(x=>x.Customer.GLCode.ID)
  124. );
  125. query.Add(
  126. new Filter<JobScope>(x => x.Job.ID).IsEqualTo(JobID)
  127. .And(x => x.Status.Approved).IsEqualTo(true),
  128. columns);
  129. query.Add(
  130. new Filter<Assignment>(x => x.JobLink.ID).IsEqualTo(JobID),
  131. Columns.None<Assignment>().Add(x => x.JobScope.ID)
  132. .Add(x => x.Actual.Duration)
  133. .Add(x => x.EmployeeLink.HourlyRate));
  134. query.Add(
  135. new Filter<Invoice>(x=>x.JobLink.ID).IsEqualTo(JobID),
  136. Columns.None<Invoice>().Add(x => x.ID).Add(x => x.Retained));
  137. if (InvoiceID != Guid.Empty)
  138. query.Add(
  139. new Filter<InvoiceLine>(x=>x.InvoiceLink.ID).IsEqualTo(InvoiceID),
  140. Columns.None<InvoiceLine>()
  141. .Add(x => x.ID)
  142. .Add(x => x.Scope.ID)
  143. .Add(x => x.ExTax));
  144. query.Query();
  145. Job = query.Get<Job>().ToObjects<Job>().FirstOrDefault() ?? new Job();
  146. Scopes = query.Get<JobScope>().ToArray<JobScope>();
  147. var assignments = query.Get<Assignment>().ToObjects<Assignment>()
  148. .GroupBy(x => x.JobScope.ID)
  149. .ToDictionary(x => x.Key, x => x.Sum(x => x.Actual.Duration.TotalHours * x.EmployeeLink.HourlyRate));
  150. Invoices = query.Get<Invoice>().ToArray<Invoice>();
  151. Invoice = Invoices.FirstOrDefault(x => x.ID == InvoiceID) ?? new Invoice();
  152. InvoiceLines = InvoiceID != Guid.Empty
  153. ? query.Get<InvoiceLine>().ToObjects<InvoiceLine>().ToList()
  154. : new List<InvoiceLine>();
  155. var items = new List<ProgressClaim>();
  156. foreach(var scope in Scopes)
  157. {
  158. var newItem = new ProgressClaim();
  159. newItem.JobScope.CopyFrom(scope);
  160. var invoiceline = InvoiceLines.FirstOrDefault(x =>
  161. x.Scope.ID == scope.ID || (x.Scope.ID == Guid.Empty && scope.ID == scope.Job.DefaultScope.ID));
  162. if (invoiceline != null)
  163. {
  164. newItem.PreviouslyClaimed = scope.InvoiceExTax - invoiceline.ExTax;
  165. newItem.PreviouslyClaimedPercent = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : (scope.InvoiceExTax - invoiceline.ExTax) / scope.ExTax * 100;
  166. newItem.PercentCost = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : scope.InvoiceExTax / scope.ExTax * 100;
  167. newItem.Cost = invoiceline.ExTax;
  168. }
  169. else
  170. {
  171. newItem.PreviouslyClaimed = scope.InvoiceExTax;
  172. newItem.PreviouslyClaimedPercent = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : scope.InvoiceExTax / scope.ExTax * 100;
  173. newItem.PercentCost = newItem.PreviouslyClaimedPercent;
  174. newItem.Cost = 0;
  175. }
  176. newItem.Materials = scope.MaterialsExTax;
  177. newItem.Labour = assignments.GetValueOrDefault(scope.ID);
  178. newItem.GLCodeID = Job.Account.ID != Guid.Empty
  179. ? Job.Account.GLCode.ID
  180. : Job.Customer.GLCode.ID;
  181. items.Add(newItem);
  182. }
  183. Items = items;
  184. CalculateTotals();
  185. _loadedData = true;
  186. }
  187. protected override void Changed()
  188. {
  189. base.Changed();
  190. CalculateTotals();
  191. }
  192. private void CalculateTotals()
  193. {
  194. Guid[] contractscopeids = Scopes.Where(x => x.Type == JobScopeType.Contract).Select(x => x.ID).ToArray();
  195. Guid[] variationscopeids = Scopes.Where(x => x.Type != JobScopeType.Contract).Select(x => x.ID).ToArray();
  196. ProjectContract = Items.Where(x => contractscopeids.Contains(x.JobScope.ID)).Sum(x => x.JobScope.ExTax);
  197. PreviousContract = Items.Where(x => contractscopeids.Contains(x.JobScope.ID)).Sum(x => x.PreviouslyClaimed);
  198. PreviousContractPercent = ProjectContract.IsEffectivelyEqual(0.0)
  199. ? 0.0
  200. : PreviousContract * 100.0 / ProjectContract;
  201. CurrentContract = Items.Where(x => contractscopeids.Contains(x.JobScope.ID)).Sum(x => x.Cost);
  202. CurrentContractPercent = ProjectContract.IsEffectivelyEqual(0.0)
  203. ? 0.0
  204. : CurrentContract * 100.0 / ProjectContract;
  205. CompletedContract = PreviousContract + CurrentContract;
  206. CompletedContractPercent = ProjectContract.IsEffectivelyEqual(0.0)
  207. ? 0.0
  208. : CompletedContract * 100.0 / ProjectContract;
  209. ProjectVariations = Items.Where(x => variationscopeids.Contains(x.JobScope.ID)).Sum(x => x.JobScope.ExTax);
  210. PreviousVariations = Items.Where(x => variationscopeids.Contains(x.JobScope.ID)).Sum(x => x.PreviouslyClaimed);
  211. PreviousVariationsPercent = ProjectVariations.IsEffectivelyEqual(0.0)
  212. ? 0.0
  213. : PreviousVariations * 100.0 / ProjectVariations;
  214. CurrentVariations = Items.Where(x => variationscopeids.Contains(x.JobScope.ID)).Sum(x => x.Cost);
  215. CurrentVariationsPercent = ProjectVariations.IsEffectivelyEqual(0.0)
  216. ? 0.0
  217. : CurrentVariations * 100.0 / ProjectVariations;
  218. CompletedVariations = PreviousVariations + CurrentVariations;
  219. CompletedVariationsPercent = ProjectVariations.IsEffectivelyEqual(0.0)
  220. ? 0.0
  221. : CompletedVariations * 100.0 / ProjectVariations;
  222. ProjectSubTotal = ProjectContract + ProjectVariations;
  223. PreviousSubTotal = PreviousContract + PreviousVariations;
  224. PreviousSubTotalPercent = ProjectSubTotal.IsEffectivelyEqual(0.0)
  225. ? 0.0
  226. : PreviousSubTotal * 100.0 / ProjectSubTotal;
  227. CurrentSubTotal = CurrentContract + CurrentVariations;
  228. CurrentSubTotalPercent = ProjectSubTotal.IsEffectivelyEqual(0.0)
  229. ? 0.0
  230. : CurrentSubTotal * 100.0 / ProjectSubTotal;
  231. CompletedSubTotal = PreviousSubTotal + CurrentSubTotal;
  232. CompletedSubTotalPercent = ProjectSubTotal.IsEffectivelyEqual(0.0)
  233. ? 0.0
  234. : CompletedSubTotal * 100.0 / ProjectSubTotal;
  235. var retentionbase = Job.Retention.IncludeVariations
  236. ? ProjectSubTotal
  237. : ProjectContract;
  238. ProjectRetention = retentionbase * Job.Retention.Maximum / 100.0;
  239. ProjectRetentionPercent = Job.Retention.Maximum;
  240. PreviousRetention = Invoices.Where(x => x.ID != InvoiceID).Sum(x=>x.Retained);
  241. PreviousRetentionPercent = retentionbase.IsEffectivelyEqual(0.0)
  242. ? 0.0
  243. : PreviousRetention * 100.0 / retentionbase;
  244. CurrentRetention = Math.Min(ProjectRetention - PreviousRetention,CurrentSubTotal * Job.Retention.Rate / 100.0);
  245. CurrentRetentionPercent = retentionbase.IsEffectivelyEqual(0.0)
  246. ? 0.0
  247. : CurrentRetention * 100.0 / retentionbase;
  248. CompletedRetention = PreviousRetention + CurrentRetention;
  249. CompletedRetentionPercent = retentionbase.IsEffectivelyEqual(0.0)
  250. ? 0.0
  251. : CompletedRetention * 100.0 / retentionbase;
  252. ProjectTotal = (ProjectSubTotal - ProjectRetention);
  253. CompletedTotal = (CompletedSubTotal - CompletedRetention);
  254. CompletedTotalPercent = ProjectTotal.IsEffectivelyEqual(0.0)
  255. ? 0.0
  256. : CompletedTotal * 100.0 / ProjectTotal;
  257. PreviousTotal = (PreviousSubTotal - PreviousRetention);
  258. PreviousTotalPercent = ProjectTotal.IsEffectivelyEqual(0.0)
  259. ? 0.0
  260. : PreviousTotal * 100.0 / ProjectTotal;
  261. CurrentTotal = (CurrentSubTotal - CurrentRetention);
  262. CurrentTotalPercent = ProjectTotal.IsEffectivelyEqual(0.0)
  263. ? 0.0
  264. : CurrentTotal * 100.0 / ProjectTotal;
  265. }
  266. protected override void Reload(Filters<ProgressClaim> criteria, Columns<ProgressClaim> columns, ref SortOrder<ProgressClaim>? sort, CancellationToken token, Action<CoreTable?, Exception?> action)
  267. {
  268. LoadData();
  269. base.Reload(criteria, columns, ref sort, token, action);
  270. }
  271. protected override void SaveColumns(DynamicGridColumns columns)
  272. {
  273. ColumnsComponent.SaveColumns(columns);
  274. }
  275. protected override void LoadColumnsMenu(ContextMenu menu)
  276. {
  277. ColumnsComponent.LoadColumnsMenu(menu);
  278. }
  279. protected override void DoDoubleClick(object sender, DynamicGridCellClickEventArgs args)
  280. {
  281. if (args.Row is null) return;
  282. if(args.Column is DynamicGridColumn gridColumn)
  283. {
  284. var claim = LoadItem(args.Row);
  285. if(new Column<ProgressClaim>(x => x.Materials).IsEqualTo(gridColumn.ColumnName))
  286. {
  287. var grid = new ProgressClaimMaterialsGrid(claim, Invoice);
  288. var window = DynamicGridUtils.CreateGridWindow($"Materials", grid);
  289. window.ShowDialog();
  290. }
  291. else if(new Column<ProgressClaim>(x => x.Labour).IsEqualTo(gridColumn.ColumnName))
  292. {
  293. var grid = new ProgressClaimLabourGrid(claim, Invoice, Job);
  294. var window = DynamicGridUtils.CreateGridWindow($"Labour", grid);
  295. window.ShowDialog();
  296. }
  297. }
  298. }
  299. public override DynamicGridColumns GenerateColumns()
  300. {
  301. var columns = new DynamicGridColumns();
  302. columns.Add<ProgressClaim, string>(x => x.JobScope.Number, 80, "Number", "", Alignment.MiddleCenter);
  303. columns.Add<ProgressClaim, string>(x => x.JobScope.Description, 0, "Description", "", Alignment.MiddleCenter);
  304. columns.Add<ProgressClaim, double>(x => x.Materials, 80, "Material $", "", Alignment.MiddleCenter);
  305. columns.Add<ProgressClaim, double>(x => x.Labour, 80, "Labour $", "", Alignment.MiddleCenter);
  306. columns.Add<ProgressClaim, double>(x => x.JobScope.ExTax, 100, "Quote $", "", Alignment.MiddleCenter);
  307. columns.Add<ProgressClaim, double>(x => x.PreviouslyClaimed, 100, "Prev $", "C2", Alignment.MiddleCenter);
  308. columns.Add<ProgressClaim, double>(x => x.PreviouslyClaimedPercent, 80, "Prev %", "", Alignment.MiddleCenter);
  309. return columns;
  310. }
  311. protected override DynamicGridColumns LoadColumns()
  312. {
  313. var columns = ColumnsComponent.LoadColumns();
  314. ActionColumns.Clear();
  315. ActionColumns.Add(new DynamicTemplateColumn(row =>
  316. {
  317. var item = LoadItem(row);
  318. var editor = new DoubleTextBox
  319. {
  320. VerticalAlignment = VerticalAlignment.Stretch,
  321. HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center,
  322. HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch,
  323. Background = new SolidColorBrush(Colors.LightYellow),
  324. BorderThickness = new Thickness(0.0),
  325. MinValue = 0.0,
  326. MaxValue = 100.0,
  327. Value = item.PercentCost
  328. };
  329. editor.ValueChanged += (o, e) =>
  330. {
  331. var maxValue = 100.0;
  332. var value = (double?)e.NewValue ?? default;
  333. if(value > maxValue)
  334. {
  335. Dispatcher.BeginInvoke(() => editor.Value = maxValue);
  336. }
  337. else
  338. {
  339. item.PercentCost = value;
  340. item.Cost = item.JobScope.ExTax * item.PercentCost / 100 - item.PreviouslyClaimed;
  341. UpdateRow(row, item);
  342. InvalidateRow(row);
  343. DoChanged();
  344. }
  345. };
  346. return editor;
  347. })
  348. {
  349. HeaderText = "Cur %",
  350. Width = 80,
  351. });
  352. ActionColumns.Add(new DynamicTextColumn(row =>
  353. {
  354. if (row is null) return null;
  355. var item = LoadItem(row);
  356. return item.Cost;
  357. })
  358. {
  359. Format = "C2",
  360. HeaderText = "Claim $",
  361. Width = 100,
  362. GetSummary = () =>
  363. {
  364. return new DynamicGridCustomSummary(rows => rows.Sum(x => LoadItem(x).Cost), "C2");
  365. }
  366. });
  367. return columns;
  368. }
  369. private DynamicGridTreeUIComponent<ProgressClaim>? _uiComponent;
  370. private DynamicGridTreeUIComponent<ProgressClaim> UIComponent
  371. {
  372. get
  373. {
  374. if(_uiComponent is null)
  375. {
  376. _uiComponent = new DynamicGridTreeUIComponent<ProgressClaim>(
  377. x => x.JobScope.ID,
  378. x => x.JobScope.Parent.ID)
  379. {
  380. Parent = this,
  381. MaxRowHeight = 30,
  382. };
  383. //_uiComponent.OnContextMenuOpening += JobDocumentSetFolderTree_OnContextMenuOpening;
  384. }
  385. return _uiComponent;
  386. }
  387. }
  388. protected override IDynamicGridUIComponent<ProgressClaim> CreateUIComponent()
  389. {
  390. return UIComponent;
  391. }
  392. }