ProgressClaimGrid.cs 18 KB

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