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