浏览代码

Added Retention Management to Jobs
Added sequence to Job Status Codes
Invoice Types re now linked to Job Types

frogsoftware 1 年之前
父节点
当前提交
965fe0ab85

+ 16 - 8
prs.classes/Entities/Invoice/Invoice.cs

@@ -80,6 +80,12 @@ namespace Comal.Classes
         public FormulaType Type => FormulaType.Virtual;
     }
 
+    public enum InvoiceType
+    {
+        Standard,
+        ProgressClaim
+    }
+
     [UserTracking("Accounts Receivable")]
     public class Invoice : Entity, IPersistent, IRemotable, INumericAutoIncrement<Invoice>, ILicense<AccountsReceivableLicense>, IExportable, IPostable
     {
@@ -100,16 +106,15 @@ namespace Comal.Classes
 
         [EditorSequence(5)]
         public CustomerLink CustomerLink { get; set; }
-
-        // [EditorSequence(6)]
-        // [DurationEditor(Editable = Editable.Hidden)]
-        // [Aggregate(typeof(InvoiceChargeableHours))]
-        // public TimeSpan ChargeableHours { get; set; }
-
-        [EditorSequence(7)]
+        
+        [EditorSequence(6)]
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Aggregate(typeof(InvoiceExTax))]
         public double ExTax { get; set; }
+        
+        [EditorSequence(7)]
+        [CurrencyEditor(Summary = Summary.Sum)]
+        public double Retained { get; set; }
 
         [EditorSequence(8)]
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
@@ -138,7 +143,7 @@ namespace Comal.Classes
         [CurrencyEditor(Editable = Editable.Hidden, Summary = Summary.Sum)]
         [Formula(typeof(InvoiceBalance))]
         public double Balance { get; set; }
-
+        
         [NullEditor]
         [LoggableProperty]
         [RequiredColumn]
@@ -153,6 +158,9 @@ namespace Comal.Classes
 
         [NullEditor]
         public string PostedReference { get; set; }
+        
+        [NullEditor]
+        public InvoiceType Type { get; set; }
 
         public Expression<Func<Invoice, int>> AutoIncrementField()
         {

+ 13 - 5
prs.classes/Entities/Job/Job.cs

@@ -47,17 +47,25 @@ namespace Comal.Classes
         [EditorSequence(6)]
         public ContactLink Contact { get; set; }
         
-        [EditorSequence(7)]
-        [Caption("Bill To")]
-        public AccountLink Account { get; set; }
         
-        [EditorSequence(8)]
+        [EditorSequence(7)]
         [TextBoxEditor]
         public String ClientReference { get; set; }
         
-        [EditorSequence(9)]
+        [EditorSequence(8)]
         public JobStatusLink JobStatus { get; set; }
 
+        #endregion
+        
+        #region Accounts
+        
+        [EditorSequence("Accounts",1)]
+        [Caption("Bill To")]
+        public AccountLink Account { get; set; }
+
+        [EditorSequence("Accounts",2)]
+        public JobRetention Retention { get; set; }
+        
         #endregion
 
         [NotesEditor]

+ 129 - 0
prs.classes/Entities/Job/JobRetention/JobRetention.cs

@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using InABox.Core;
+
+namespace Comal.Classes
+{
+    public class JobRetentionType : Entity, IRemotable, IPersistent, ILicense<ProjectManagementLicense>, ISequenceable
+    {
+        [TextBoxEditor] 
+        [EditorSequence(1)] 
+        public string Description { get; set; }
+
+        [DoubleEditor] 
+        [EditorSequence(2)] 
+        public double Rate { get; set; }
+
+        [DoubleEditor] 
+        [EditorSequence(3)] 
+        public double Maximum { get; set; }
+
+        [DoubleEditor] 
+        [EditorSequence(4)] 
+        public double Completion { get; set; }
+
+        [IntegerEditor] 
+        [EditorSequence(5)] 
+        public int Release { get; set; }
+        
+        [CheckBoxEditor]
+        [EditorSequence(6)]
+        public bool IncludeVariations { get; set; }
+
+        [NullEditor] 
+        public long Sequence { get; set; }
+    }
+
+    public class JobRetentionTypeLink : EntityLink<JobRetentionType>
+    {
+        [LookupEditor(typeof(JobRetentionType))]
+        public override Guid ID { get; set; }
+
+        [TextBoxEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
+        [EditorSequence(1)]
+        public string Description { get; set; }
+
+        [DoubleEditor(Visible = Visible.Hidden, Editable = Editable.Hidden)]
+        [EditorSequence(2)]
+        public double Rate { get; set; }
+
+        [DoubleEditor(Visible = Visible.Hidden, Editable = Editable.Hidden)]
+        [EditorSequence(3)]
+        public double Maximum { get; set; }
+
+        [DoubleEditor(Visible = Visible.Hidden, Editable = Editable.Hidden)]
+        [EditorSequence(4)]
+        public double Completion { get; set; }
+
+        [IntegerEditor(Visible = Visible.Hidden, Editable = Editable.Hidden)]
+        [EditorSequence(5)]
+        public int Release { get; set; }
+        
+        [CheckBoxEditor(Visible = Visible.Hidden, Editable = Editable.Hidden)]
+        [EditorSequence(6)]
+        public bool IncludeVariations { get; set; }
+    }
+
+    public class JobRetentionTypeLookups : ILookupDefinition<JobRetentionType>
+    {
+        public SortOrder<JobRetentionType>? DefineSortOrder()
+        {
+            return new SortOrder<JobRetentionType>(x => x.Sequence);
+        }
+
+        public Filter<JobRetentionType>? DefineFilter()
+        {
+            return null;
+        }
+
+        public Columns<JobRetentionType> DefineColumns()
+        {
+            return Columns.All<JobRetentionType>();
+        }
+
+        public Columns<JobRetentionType> RequiredColumns()
+        {
+            return Columns.All<JobRetentionType>();
+        }
+
+        public string FormatLookup(Dictionary<string, object?> values, IEnumerable<string> exclude)
+        {
+            return $"{values["Description"]}";
+        }
+    }
+
+    public class JobRetention : EnclosedEntity
+    {
+        [EditorSequence(1)] public JobRetentionTypeLink Type { get; set; }
+
+        [DoubleEditor]
+        [EditorSequence(2)]
+        public double Rate { get; set; }
+
+        [DoubleEditor]
+        [EditorSequence(3)]
+        public double Maximum { get; set; }
+
+        [DoubleEditor]
+        [EditorSequence(4)]
+        public double Completion { get; set; }
+
+        [IntegerEditor]
+        [EditorSequence(5)]
+        public int Release { get; set; }
+        
+        [CheckBoxEditor]
+        [EditorSequence(6)]
+        public bool IncludeVariations { get; set; }
+
+        static JobRetention()
+        {
+            LinkedProperties.Register<JobRetention, JobRetentionTypeLink, double>(x => x.Type, x => x.Rate, x => x.Rate);
+            LinkedProperties.Register<JobRetention, JobRetentionTypeLink, double>(x => x.Type, x => x.Maximum, x => x.Maximum);
+            LinkedProperties.Register<JobRetention, JobRetentionTypeLink, double>(x => x.Type, x => x.Completion, x => x.Completion);
+            LinkedProperties.Register<JobRetention, JobRetentionTypeLink, int>(x => x.Type, x => x.Release, x => x.Release);
+            LinkedProperties.Register<JobRetention, JobRetentionTypeLink, bool>(x => x.Type, x => x.IncludeVariations, x => x.IncludeVariations);
+
+        }
+    }
+}

+ 50 - 20
prs.classes/Entities/Job/JobScopes/JobScope.cs

@@ -1,11 +1,14 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using System.Linq.Expressions;
+using InABox.Clients;
 using InABox.Core;
 
 namespace Comal.Classes
 {
-    public class JobFinancialInvoiceExTax : CoreAggregate<JobScope, InvoiceLine, double>
+    
+    public class JobScopeInvoiceExTax : CoreAggregate<JobScope, InvoiceLine, double>
     {
         public override Expression<Func<InvoiceLine, double>> Aggregate => x => x.ExTax;
 
@@ -18,7 +21,7 @@ namespace Comal.Classes
             };
     }
 
-    public class JobFinancialInvoiceTax : CoreAggregate<JobScope, InvoiceLine, double>
+    public class JobScopeInvoiceTax : CoreAggregate<JobScope, InvoiceLine, double>
     {
         public override Expression<Func<InvoiceLine, double>> Aggregate => x => x.Tax;
 
@@ -31,7 +34,7 @@ namespace Comal.Classes
             };
     }
 
-    public class JobFinancialInvoiceIncTax : CoreAggregate<JobScope, InvoiceLine, double>
+    public class JobScopeInvoiceIncTax : CoreAggregate<JobScope, InvoiceLine, double>
     {
         public override Expression<Func<InvoiceLine, double>> Aggregate => x => x.IncTax;
 
@@ -44,7 +47,7 @@ namespace Comal.Classes
             };
     }
 
-    public class JobFinancialUninvoicedExTax : IFormula<JobScope, double>
+    public class JobScopeUninvoicedExTax : IFormula<JobScope, double>
     {
         public Expression<Func<JobScope, double>> Value => x => x.ExTax;
         public Expression<Func<JobScope, double>>[] Modifiers => new Expression<Func<JobScope, double>>[] { x => x.InvoiceExTax };
@@ -90,27 +93,62 @@ namespace Comal.Classes
         [MemoEditor(Visible = Visible.Default)]
         [EditorSequence(3)]
         public string Description { get; set; }
+        
+        private class JobScopeLookup : LookupDefinitionGenerator<JobScope, JobScope>
+        {
+            public override Filter<JobScope> DefineFilter(JobScope[] items)
+            {
+                if (items?.Any() != true)
+                    return Filter.None<JobScope>();
+
+                var data = Client.Query(
+                    new Filter<JobScope>(x=>x.Job.ID).IsEqualTo(items.First().Job.ID),
+                    Columns.None<JobScope>().Add(x => x.ID).Add(x => x.Parent.ID));
+
+                var nodes = new CoreTreeNodes();
+                nodes.Load<JobScope>(data, x => x.ID, x => x.Parent.ID);
 
+                var exclusions = items.SelectMany(x => nodes.GetChildren(x.ID)).ToArray();
+                var ids = exclusions.Select(x => x.ID).ToArray();
+                return new Filter<JobScope>(x=>x.Job.ID).IsEqualTo(items.First().Job.ID).And(x => x.ID).NotInList(ids);
+            }
+
+            public override Columns<JobScope> DefineFilterColumns()
+                => Columns.None<JobScope>().Add(x => x.ID).Add(x => x.Parent.ID).Add(x => x.Number);
+        }
+        
+        [LookupDefinition(typeof(JobScopeLookup))]
+        [EditorSequence(4)]
+        public JobScopeLink Parent { get; set; }
+        
+        [CurrencyEditor(Summary = Summary.Sum, Visible = Visible.Default)]
         [EditorSequence(5)]
-        public TaxCodeLink TaxCode { get; set; }
+        public double ExTax { get; set; }
 
+        [EditorSequence(6)]
+        public TaxCodeLink TaxCode { get; set; }
+        
+        [CurrencyEditor(Summary = Summary.Sum, Visible = Visible.Optional)]
         [EditorSequence(7)]
-        public JobScopeStatusLink Status { get; set; }
+        public double IncTax { get; set; }
 
+        [EditorSequence(8)]
+        public JobScopeStatusLink Status { get; set; }
+        
         [CurrencyEditor(Visible = Visible.Default, Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(JobFinancialInvoiceExTax))]
+        [Aggregate(typeof(JobScopeInvoiceExTax))]
         public double InvoiceExTax { get; set; }
 
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(JobFinancialInvoiceTax))]
+        [Aggregate(typeof(JobScopeInvoiceTax))]
         public double InvoiceTax { get; set; }
 
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Aggregate(typeof(JobFinancialInvoiceIncTax))]
+        [Aggregate(typeof(JobScopeInvoiceIncTax))]
         public double InvoiceIncTax { get; set; }
 
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden, Summary = Summary.Sum)]
-        [Formula(typeof(JobFinancialUninvoicedExTax))]
+        [Formula(typeof(JobScopeUninvoicedExTax))]
         public double UninvoiceIncTax { get; set; }
 
         [CurrencyEditor(Visible = Visible.Optional, Editable = Editable.Hidden, Summary = Summary.Sum)]
@@ -121,21 +159,13 @@ namespace Comal.Classes
         public Filter<JobScope> AutoIncrementFilter() =>  new Filter<JobScope>(x => x.Job.ID).IsEqualTo(Job.ID);
         public String AutoIncrementPrefix() => "";
         public string AutoIncrementFormat() => "{0:D3}";
-
-        [CurrencyEditor(Summary = Summary.Sum, Visible = Visible.Default)]
-        [EditorSequence(4)]
-        public double ExTax { get; set; }
-
+        
         [NullEditor]
         public double TaxRate { get; set; }
 
         [NullEditor(Summary = Summary.Sum, Visible = Visible.Optional)]
         public double Tax { get; set; }
-
-        [CurrencyEditor(Summary = Summary.Sum, Visible = Visible.Optional)]
-        [EditorSequence(6)]
-        public double IncTax { get; set; }
-
+        
         static JobScope()
         {
             LinkedProperties.Register<JobScope, TaxCodeLink, double>(x=>x.TaxCode, x => x.Rate, x => x.TaxRate);

+ 12 - 0
prs.classes/Entities/Job/JobScopes/JobScopeLink.cs

@@ -3,6 +3,13 @@ using InABox.Core;
 
 namespace Comal.Classes
 {
+    
+    public class JobScopeLinkStub : EntityLink<JobScope>
+    {
+        [NullEditor]
+        public override Guid ID { get; set; }
+    }
+    
     public class JobScopeLink : EntityLink<JobScope>
     {
         [LookupEditor(typeof(JobScope))]
@@ -24,5 +31,10 @@ namespace Comal.Classes
         public double ExTax { get; set; }
 
         public TaxCodeLink TaxCode { get; set; }
+        
+        public JobScopeLinkStub Parent { get; set; }
+        
     }
+
+    
 }

+ 3 - 1
prs.classes/Entities/Job/JobStatus/JobStatus.cs

@@ -3,7 +3,7 @@
 namespace Comal.Classes
 {
     [UserTracking(typeof(Job))]
-    public class JobStatus : Entity, IRemotable, IPersistent, IJobStatus, ILicense<ProjectManagementLicense>, IExportable, IImportable, IMergeable
+    public class JobStatus : Entity, IRemotable, IPersistent, IJobStatus, ILicense<ProjectManagementLicense>, IExportable, IImportable, IMergeable, ISequenceable
     {
         [UniqueCodeEditor(Visible = Visible.Default, Editable = Editable.Enabled)]
         [EditorSequence(1)]
@@ -21,5 +21,7 @@ namespace Comal.Classes
         [EditorSequence(4)]
         public bool Default { get; set; }
 
+        [NullEditor]
+        public long Sequence { get; set; }
     }
 }

+ 73 - 44
prs.desktop/Panels/Invoices/InvoiceGrid.cs

@@ -14,7 +14,7 @@ using System.Windows.Media.Imaging;
 using InABox.Configuration;
 using System.Collections.Generic;
 using System.Threading;
-using PRSDesktop.Panels.Invoices;
+using PRSDesktop;
 
 namespace PRSDesktop
 {
@@ -89,63 +89,92 @@ namespace PRSDesktop
 
         protected override void DoAdd(bool OpenEditorOnDirectEdit = false)
         {
-            if(Master == null || Master.ID == Guid.Empty)
+            if(Master == null || Master.ID == Guid.Empty || Master.JobType == JobType.Service)
             {
                 base.DoAdd(OpenEditorOnDirectEdit);
             }
             else
             {
-                var menu = new ContextMenu();
-                menu.AddItem("Invoice", null, () => base.DoAdd(OpenEditorOnDirectEdit));
-                menu.AddItem("Progress Claim", null, () =>
+                var window = new ProgressClaimWindow(Master.ID, Guid.Empty);
+                if(window.ShowDialog() == true)
                 {
-                    var grid = new ProgressClaimGrid
+                    var invoice = new Invoice();
+                    invoice.Type = InvoiceType.ProgressClaim;
+                    invoice.Description = $"Progress claim for {DateTime.Today:MMM yyyy}";
+                    invoice.JobLink.CopyFrom(Master);
+                    invoice.CustomerLink.CopyFrom(Master.Account);
+                    invoice.Retained = window.Retained;
+
+                    var lines = new List<InvoiceLine>();
+                    foreach(var item in window.Items.Where(x => !x.Cost.IsEffectivelyEqual(0.0)))
                     {
-                        JobID = Master.ID
-                    };
-                    var window = new DynamicContentDialog(grid)
-                    {
-                        Title = "Create progress claim",
-                        Width = 1000,
-                        Height = 500
-                    };
-                    grid.OnChanged += (o, e) =>
-                    {
-                        window.CanSave = grid.Items.Any(x => !x.PercentCost.IsEffectivelyEqual(x.PreviouslyClaimedPercent));
-                    };
-                    grid.Refresh(true, true);
-                    if(window.ShowDialog() == true)
-                    {
-                        var invoice = new Invoice();
-                        invoice.Description = $"Progress claim for {DateTime.Today:MMM yyyy}";
-                        invoice.JobLink.CopyFrom(Master);
-                        invoice.CustomerLink.CopyFrom(Master.Account);
+                        var line = new InvoiceLine();
 
-                        var lines = new List<InvoiceLine>();
-                        foreach(var item in grid.Items.Where(x => !x.Cost.IsEffectivelyEqual(0.0)))
-                        {
-                            var line = new InvoiceLine();
+                        line.Description = item.JobScope.Description;
+                        line.ExTax = item.Cost;
+                        line.TaxCode.CopyFrom(item.JobScope.TaxCode);
+                        line.Scope.CopyFrom(item.JobScope);
 
-                            line.Description = item.JobScope.Description;
-                            line.ExTax = item.Cost;
-                            line.TaxCode.CopyFrom(item.JobScope.TaxCode);
-                            line.Scope.CopyFrom(item.JobScope);
+                        lines.Add(line);
+                    }
 
-                            lines.Add(line);
-                        }
+                    Client.Save(invoice, "Progress claim created by user.");
 
-                        Client.Save(invoice, "Progress claim created by user.");
+                    foreach(var line in lines)
+                    {
+                        line.InvoiceLink.ID = invoice.ID;
+                    }
+                    Client.Save(lines, "Progress claim created by user.");
 
-                        foreach(var line in lines)
-                        {
-                            line.InvoiceLink.ID = invoice.ID;
-                        }
-                        Client.Save(lines, "Progress claim created by user.");
+                    Refresh(false, true);
+                }
 
-                        Refresh(false, true);
+            }
+        }
+
+        protected override void DoEdit()
+        {
+            if(Master == null || Master.ID == Guid.Empty || Master.JobType == JobType.Service)
+            {
+                base.DoEdit();
+            }
+            else
+            {
+                
+                Guid invoiceid = SelectedRows.FirstOrDefault()?.Get<Invoice, Guid>(x => x.ID) ?? Guid.Empty;
+                var window = new ProgressClaimWindow(Master.ID, invoiceid);
+
+                if(window.ShowDialog() == true)
+                {
+                    var invoice = Client.Query(
+                        new Filter<Invoice>(x => x.ID).IsEqualTo(invoiceid),
+                        Columns.Required<Invoice>()
+                    ).ToObjects<Invoice>().FirstOrDefault();
+                    invoice.Retained = window.Retained;
+                    Client.Save(invoice,"Progress claim updated by user.");
+                    
+                    var lines = Client.Query<InvoiceLine>(
+                        new Filter<InvoiceLine>(x=>x.InvoiceLink.ID).IsEqualTo(invoiceid),
+                        Columns.None<InvoiceLine>().Add(x=>x.ID)).ToObjects<InvoiceLine>().ToList();
+                    
+                    Client.Delete(lines,"Invoice Updated");
+                    lines.Clear();
+                    
+                    foreach(var item in window.Items.Where(x => !x.Cost.IsEffectivelyEqual(0.0)))
+                    {
+                        var line = new InvoiceLine();
+                        line.InvoiceLink.ID = window.InvoiceID;
+                        line.Description = item.JobScope.Description;
+                        line.ExTax = item.Cost;
+                        line.TaxCode.CopyFrom(item.JobScope.TaxCode);
+                        line.Scope.CopyFrom(item.JobScope);
+                        lines.Add(line);
                     }
-                });
-                menu.IsOpen = true;
+                    Client.Save(lines, "Progress claim updated by user.");
+
+                    Refresh(false, true);
+                }
+
             }
         }
 

+ 261 - 26
prs.desktop/Panels/Invoices/ProgressClaimGrid.cs

@@ -14,26 +14,28 @@ using System.Windows.Controls;
 using System.Windows.Forms;
 using System.Windows.Media;
 
-namespace PRSDesktop.Panels.Invoices;
+namespace PRSDesktop;
 
 public class ProgressClaim : BaseObject
 {
     public JobScopeLink JobScope { get; set; }
 
-    [CurrencyEditor]
+    [CurrencyEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
     public double Materials { get; set; }
 
-    [CurrencyEditor]
+    [CurrencyEditor(Editable = Editable.Disabled, Summary = Summary.Sum)]
     public double Labour { get; set; }
 
+    [DoubleEditor(Editable = Editable.Disabled,Summary = Summary.Sum)]
     public double PreviouslyClaimed { get; set; }
 
+    [DoubleEditor(Editable = Editable.Disabled)]
     public double PreviouslyClaimedPercent { get; set; }
 
-    [DoubleEditor]
+    [DoubleEditor(Editable = Editable.Enabled)]
     public double PercentCost { get; set; }
 
-    [CurrencyEditor]
+    [DoubleEditor(Editable = Editable.Disabled,Summary = Summary.Sum)]
     public double Cost { get; set; }
 }
 
@@ -42,6 +44,48 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
     private DynamicGridCustomColumnsComponent<ProgressClaim> ColumnsComponent;
 
     public Guid JobID { get; set; }
+    
+    public Guid InvoiceID { get; set; }
+    
+    public double ProjectContract { get; private set; }
+    public double ProjectVariations { get; private set; }
+    public double ProjectSubTotal { get; private set; }
+    public double ProjectRetention { get; private set; }
+    public double ProjectRetentionPercent { get; private set; }
+    public double ProjectTotal { get; private set; }
+    
+    public double CompletedContract { get; private set; }
+    public double CompletedContractPercent { get; private set; }
+    public double CompletedVariations { get; private set; }
+    public double CompletedVariationsPercent { get; private set; }
+    public double CompletedSubTotal { get; private set; }
+    public double CompletedSubTotalPercent { get; private set; }
+    public double CompletedRetention { get; private set; }
+    public double CompletedRetentionPercent { get; private set; }
+    public double CompletedTotal { get; private set; }
+    public double CompletedTotalPercent { get; private set; }
+    
+    public double PreviousContract { get; private set; }
+    public double PreviousContractPercent { get; private set; }
+    public double PreviousVariations { get; private set; }
+    public double PreviousVariationsPercent { get; private set; }
+    public double PreviousSubTotal { get; private set; }
+    public double PreviousSubTotalPercent { get; private set; }
+    public double PreviousRetention { get; private set; }
+    public double PreviousRetentionPercent { get; private set; }
+    public double PreviousTotal { get; private set; }
+    public double PreviousTotalPercent { get; private set; }
+    
+    public double CurrentContract { get; private set; }
+    public double CurrentContractPercent { get; private set; }
+    public double CurrentVariations { get; private set; }
+    public double CurrentVariationsPercent { get; private set; }
+    public double CurrentSubTotal { get; private set; }
+    public double CurrentSubTotalPercent { get; private set; }
+    public double CurrentRetention { get; private set; }
+    public double CurrentRetentionPercent { get; private set; }
+    public double CurrentTotal { get; private set; }
+    public double CurrentTotalPercent { get; private set; }
 
     protected override void Init()
     {
@@ -52,10 +96,15 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
     protected override void DoReconfigure(DynamicGridOptions options)
     {
         base.DoReconfigure(options);
-
-        options.SelectColumns = true;
+        options.SelectColumns = false;
     }
 
+    private Job job;
+    private JobScope[] scopes;
+    private Invoice[] invoices;
+    private Invoice invoice;
+    private List<InvoiceLine> invoicelines;
+    
     private bool _loadedData = false;
     private void LoadData()
     {
@@ -63,10 +112,15 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
             Columns.None<JobScope>()
                 .Add(x => x.ID)
                 .Add(x => x.Number)
-                .Add(x => x.ExTax).Add(x => x.InvoiceExTax)
-                .Add(x => x.TaxCode.ID).Add(x => x.TaxCode.Rate)
+                .Add(x => x.ExTax)
+                .Add(x => x.InvoiceExTax)
+                .Add(x => x.TaxCode.ID)
+                .Add(x => x.TaxCode.Rate)
+                .Add(x=>x.Tax)
                 .Add(x => x.Description)
-                .Add(x => x.UninvoicedMaterialsExTax);
+                .Add(x => x.UninvoicedMaterialsExTax)
+                .Add(x => x.Job.DefaultScope.ID)
+                .Add(x=>x.Type);
 
         var scopeColumn = new Column<ProgressClaim>(x => x.JobScope).Property + ".";
         foreach(var column in DataColumns().Where(x => x.Property.StartsWith(scopeColumn)))
@@ -74,40 +128,189 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
             columns.Add(column.Property[scopeColumn.Length..]);
         }
 
-        var scopes = Client.Query<JobScope>(
+        MultiQuery query = new MultiQuery();
+        
+        query.Add(
+            new Filter<Job>(x=>x.ID).IsEqualTo(JobID),
+            Columns.None<Job>()
+                .Add(x=>x.Retention.Maximum)
+                .Add(x=>x.Retention.Rate)
+                .Add(x=>x.Retention.IncludeVariations)
+        );
+        
+        query.Add(
                 new Filter<JobScope>(x => x.Job.ID).IsEqualTo(JobID)
                     .And(x => x.Status.Approved).IsEqualTo(true),
-                columns)
-            .ToArray<JobScope>();
-
-        var assignments = Client.Query(
-            new Filter<Assignment>(x => x.JobScope.ID).InList(scopes.ToArray(x => x.ID))
+                columns);
+        
+        query.Add(
+            new Filter<Assignment>(x => x.JobLink.ID).IsEqualTo(JobID)
                 .And(x => x.Invoice.ID).IsEqualTo(Guid.Empty),
             Columns.None<Assignment>().Add(x => x.JobScope.ID)
                 .Add(x => x.Actual.Duration)
-                .Add(x => x.EmployeeLink.HourlyRate))
-            .ToObjects<Assignment>()
+                .Add(x => x.EmployeeLink.HourlyRate));
+
+        query.Add(new Filter<Invoice>(x=>x.JobLink.ID).IsEqualTo(JobID));
+        
+        if (InvoiceID != Guid.Empty)
+            query.Add(new Filter<InvoiceLine>(x=>x.InvoiceLink.ID).IsEqualTo(InvoiceID));
+        
+        query.Query();
+
+        job = query.Get<Job>().ToObjects<Job>().FirstOrDefault() ?? new Job();
+
+        scopes = query.Get<JobScope>().ToArray<JobScope>();
+
+        var assignments = query.Get<Assignment>().ToObjects<Assignment>()
             .GroupBy(x => x.JobScope.ID)
             .ToDictionary(x => x.Key, x => x.Sum(x => x.Actual.Duration.TotalHours * x.EmployeeLink.HourlyRate));
 
+        invoices = query.Get<Invoice>().ToArray<Invoice>();
+        
+        invoice = invoices.FirstOrDefault(x => x.ID == InvoiceID) ?? new Invoice();
+        
+        invoicelines = InvoiceID != Guid.Empty
+            ? query.Get<InvoiceLine>().ToObjects<InvoiceLine>().ToList()
+            : new List<InvoiceLine>();
+        
+        
         var items = new List<ProgressClaim>();
         foreach(var scope in scopes)
         {
             var newItem = new ProgressClaim();
             newItem.JobScope.CopyFrom(scope);
-            newItem.PreviouslyClaimed = scope.InvoiceExTax;
-            newItem.PreviouslyClaimedPercent = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : scope.InvoiceExTax / scope.ExTax * 100;
-            newItem.PercentCost = newItem.PreviouslyClaimedPercent;
-            newItem.Cost = 0;
+            
+            var invoiceline = invoicelines.FirstOrDefault(x =>
+                x.Scope.ID == scope.ID || (x.Scope.ID == Guid.Empty && scope.ID == scope.Job.DefaultScope.ID));
+            if (invoiceline != null)
+            {
+                newItem.PreviouslyClaimed = scope.InvoiceExTax - invoiceline.ExTax;
+                newItem.PreviouslyClaimedPercent = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : (scope.InvoiceExTax - invoiceline.ExTax) / scope.ExTax * 100;
+                newItem.PercentCost = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : scope.InvoiceExTax / scope.ExTax * 100;
+                newItem.Cost = invoiceline.ExTax;
+            }
+            else
+            {
+                newItem.PreviouslyClaimed = scope.InvoiceExTax;
+                newItem.PreviouslyClaimedPercent = scope.ExTax.IsEffectivelyEqual(0.0) ? 0.0 : scope.InvoiceExTax / scope.ExTax * 100;
+                newItem.PercentCost = newItem.PreviouslyClaimedPercent;
+                newItem.Cost = 0;
+            }
             newItem.Materials = scope.UninvoicedMaterialsExTax;
             newItem.Labour = assignments.GetValueOrDefault(scope.ID);
+            
             items.Add(newItem);
         }
         Items = items;
 
+        CalculateTotals();
+        
         _loadedData = true;
     }
 
+    protected override void Changed()
+    {
+        base.Changed();
+        CalculateTotals();
+    }
+
+    private void CalculateTotals()
+    {
+        Guid[] contractscopeids = scopes.Where(x => x.Type == JobScopeType.Contract).Select(x => x.ID).ToArray();
+        Guid[] variationscopeids = scopes.Where(x => x.Type != JobScopeType.Contract).Select(x => x.ID).ToArray();
+        
+        ProjectContract = Items.Where(x => contractscopeids.Contains(x.JobScope.ID)).Sum(x => x.JobScope.ExTax);
+        
+        PreviousContract = Items.Where(x => contractscopeids.Contains(x.JobScope.ID)).Sum(x => x.PreviouslyClaimed);
+        PreviousContractPercent = ProjectContract.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : PreviousContract * 100.0 / ProjectContract;
+        
+        CurrentContract = Items.Where(x => contractscopeids.Contains(x.JobScope.ID)).Sum(x => x.Cost);
+        CurrentContractPercent = ProjectContract.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CurrentContract * 100.0 / ProjectContract;
+        
+        CompletedContract = PreviousContract + CurrentContract;
+        CompletedContractPercent = ProjectContract.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CompletedContract * 100.0 / ProjectContract;
+        
+        ProjectVariations = Items.Where(x => variationscopeids.Contains(x.JobScope.ID)).Sum(x => x.JobScope.ExTax);
+        
+        PreviousVariations = Items.Where(x => variationscopeids.Contains(x.JobScope.ID)).Sum(x => x.PreviouslyClaimed);
+        PreviousVariationsPercent = ProjectVariations.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : PreviousVariations * 100.0 / ProjectVariations;
+        
+        CurrentVariations = Items.Where(x => variationscopeids.Contains(x.JobScope.ID)).Sum(x => x.Cost);
+        CurrentVariationsPercent = ProjectVariations.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CurrentVariations * 100.0 / ProjectVariations;
+        
+        CompletedVariations = PreviousVariations + CurrentVariations;
+        CompletedVariationsPercent = ProjectVariations.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CompletedVariations * 100.0 / ProjectVariations;
+        
+        
+        ProjectSubTotal = ProjectContract + ProjectVariations;
+        
+        PreviousSubTotal = PreviousContract + PreviousVariations;
+        PreviousSubTotalPercent = ProjectSubTotal.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : PreviousSubTotal * 100.0 / ProjectSubTotal;
+        
+        CurrentSubTotal = CurrentContract + CurrentVariations;
+        CurrentSubTotalPercent = ProjectSubTotal.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CurrentSubTotal * 100.0 / ProjectSubTotal;
+        
+        CompletedSubTotal = PreviousSubTotal + CurrentSubTotal;
+        CompletedSubTotalPercent = ProjectSubTotal.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CompletedSubTotal * 100.0 / ProjectSubTotal;
+        
+        var retentionbase = job.Retention.IncludeVariations
+            ? ProjectSubTotal
+            : ProjectContract;
+        ProjectRetention = retentionbase * job.Retention.Maximum / 100.0;
+        ProjectRetentionPercent = job.Retention.Maximum;
+        
+        
+        PreviousRetention = invoices.Where(x => x.ID != InvoiceID).Sum(x=>x.Retained);
+        PreviousRetentionPercent = retentionbase.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : PreviousRetention * 100.0 / retentionbase;
+        
+        CurrentRetention = Math.Min(ProjectRetention - PreviousRetention,CurrentSubTotal * job.Retention.Rate / 100.0);
+        CurrentRetentionPercent = retentionbase.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CurrentRetention * 100.0 / retentionbase;
+        
+        CompletedRetention = PreviousRetention + CurrentRetention;
+        CompletedRetentionPercent = retentionbase.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CompletedRetention * 100.0 / retentionbase;
+        
+        ProjectTotal = (ProjectSubTotal - ProjectRetention);
+        
+        CompletedTotal = (CompletedSubTotal - CompletedRetention);
+        CompletedTotalPercent = ProjectTotal.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CompletedTotal * 100.0 / ProjectTotal;
+        
+        PreviousTotal = (PreviousSubTotal - PreviousRetention);
+        PreviousTotalPercent = ProjectTotal.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : PreviousTotal * 100.0 / ProjectTotal;
+        
+        CurrentTotal = (CurrentSubTotal - CurrentRetention);
+        CurrentTotalPercent = ProjectTotal.IsEffectivelyEqual(0.0)
+            ? 0.0
+            : CurrentTotal * 100.0 / ProjectTotal;
+    }
+
     protected override void Reload(Filters<ProgressClaim> criteria, Columns<ProgressClaim> columns, ref SortOrder<ProgressClaim>? sort, CancellationToken token, Action<CoreTable?, Exception?> action)
     {
         LoadData();
@@ -144,7 +347,7 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
         var columns = ColumnsComponent.LoadColumns();
 
         ActionColumns.Clear();
-
+        
         ActionColumns.Add(new DynamicTemplateColumn(row =>
         {
             var item = LoadItem(row);
@@ -176,17 +379,18 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
                     DoChanged();
                 }
             };
-
+        
             return editor;
         })
         {
             HeaderText = "Cur %",
-            Width = 80
+            Width = 80,
         });
+        
         ActionColumns.Add(new DynamicTextColumn(row =>
         {
             if (row is null) return null;
-
+        
             var item = LoadItem(row);
             return item.Cost;
         })
@@ -198,4 +402,35 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
 
         return columns;
     }
+    
+    private DynamicGridTreeUIComponent<ProgressClaim>? _uiComponent;
+    
+
+
+    private DynamicGridTreeUIComponent<ProgressClaim> UIComponent
+    {
+        get
+        {
+            if(_uiComponent is null)
+            {
+                _uiComponent = new DynamicGridTreeUIComponent<ProgressClaim>(
+                    x => x.JobScope.ID,
+                    x => x.JobScope.Parent.ID)
+                {
+                    Parent = this,
+                    MaxRowHeight = 30,
+                };
+
+                //_uiComponent.OnContextMenuOpening += JobDocumentSetFolderTree_OnContextMenuOpening;
+            }
+            return _uiComponent;
+        }
+    }
+    
+    protected override IDynamicGridUIComponent<ProgressClaim> CreateUIComponent()
+    {
+        return UIComponent;
+    }
+
+    
 }

+ 534 - 0
prs.desktop/Panels/Invoices/ProgressClaimWindow.xaml

@@ -0,0 +1,534 @@
+<Window x:Class="PRSDesktop.ProgressClaimWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:local="clr-namespace:PRSDesktop"
+        mc:Ignorable="d"
+        Title="Progress Claim Details" Height="600" Width="1000">
+    <Grid
+        Margin="5">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        <local:ProgressClaimGrid
+            x:Name="Grid"
+            Grid.Row="0"
+            Grid.Column="0"
+            OnChanged="Grid_OnChanged"
+            AfterRefresh="Grid_OnAfterRefresh"/>
+        
+        <Border
+            Grid.Row="1"
+            Grid.Column="0"
+            Margin="0,5,0,0"
+            BorderBrush="Gray"
+            BorderThickness="0.75">
+        
+            <Grid 
+                Margin="5">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="29"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="50"/>
+                    <ColumnDefinition Width="20"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="50"/>
+                    <ColumnDefinition Width="20"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="50"/>
+                    <ColumnDefinition Width="20"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="50"/>
+                </Grid.ColumnDefinitions>
+                <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="35" />
+                    <RowDefinition Height="35" />
+                    <RowDefinition Height="35" />
+                    <RowDefinition Height="35" />
+                    <RowDefinition Height="35" />
+                </Grid.RowDefinitions>
+
+                <Label
+                    Grid.Row="0"
+                    Grid.Column="2"
+                    Grid.ColumnSpan="2"
+                    Margin="0,0,0,0"
+                    Content="Scope of Works"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+ 
+                <Label
+                    Grid.Row="0"
+                    Grid.Column="5"
+                    Grid.ColumnSpan="2"
+                    Margin="0,0,0,0"
+                    Content="Completed Value"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+
+                <Label
+                    Grid.Row="0"
+                    Grid.Column="8"
+                    Grid.ColumnSpan="2"
+                    Margin="0,0,0,0"
+                    Content="Previous Claims"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+
+                <Label
+                    Grid.Row="0"
+                    Grid.Column="11"
+                    Grid.ColumnSpan="2"
+                    Margin="0,0,0,0"
+                    Content="This Claim"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <Border
+                    Grid.Row="1"
+                    Grid.Column="0"
+                    Grid.ColumnSpan="13"
+                    BorderBrush="Gray"
+                    BorderThickness="0,0,0,0.75"
+                    Margin="-5,5,-5,0"/>
+                
+                <Label
+                    Grid.Row="2"
+                    Grid.Column="0"
+                    Margin="0,0,0,0"
+                    Content="Contract"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <Label
+                    Grid.Row="3"
+                    Grid.Column="0"
+                    Margin="0,0,0,0"
+                    Content="Variations"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <Label
+                    Grid.Row="4"
+                    Grid.Column="0"
+                    Margin="0,5,0,0"
+                    Content="Sub Total"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+
+                <Label
+                    Grid.Row="5"
+                    Grid.Column="0"
+                    Margin="0,5,0,0"
+                    Content="Retention"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <Label
+                    Grid.Row="7"
+                    Grid.Column="0"
+                    Margin="0,5,0,0"
+                    Content="Total Claim"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="ProjectContract"
+                    Grid.Row="2"
+                    Grid.Column="2"
+                    Grid.ColumnSpan="2"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="ProjectVariations"
+                    Grid.Row="3"
+                    Grid.Column="2"
+                    Grid.ColumnSpan="2"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="ProjectSubTotal"
+                    Grid.Row="4"
+                    Grid.Column="2"
+                    Grid.ColumnSpan="2"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="ProjectRetention"
+                    Grid.Row="5"
+                    Grid.Column="2"
+                    Margin="5,5,0,0" 
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="ProjectRetentionPercent"
+                    Grid.Row="5"
+                    Grid.Column="3"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="ProjectTotal"
+                    Grid.Row="6"
+                    Grid.Column="2"
+                    Grid.ColumnSpan="2"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+                
+                <TextBox
+                    x:Name="CompletedContract"
+                    Grid.Row="2"
+                    Grid.Column="5"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CompletedContractPercent"
+                    Grid.Row="2"
+                    Grid.Column="6"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CompletedVariations"
+                    Grid.Row="3"
+                    Grid.Column="5"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CompletedVariationsPercent"
+                    Grid.Row="3"
+                    Grid.Column="6"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CompletedSubTotal"
+                    Grid.Row="4"
+                    Grid.Column="5"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                               
+                <TextBox
+                    x:Name="CompletedSubTotalPercent"
+                    Grid.Row="4"
+                    Grid.Column="6"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/> 
+                
+                <TextBox
+                    x:Name="CompletedRetention"
+                    Grid.Row="5"
+                    Grid.Column="5"
+                    Margin="5,5,0,0" 
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CompletedRetentionPercent"
+                    Grid.Row="5"
+                    Grid.Column="6"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CompletedTotal"
+                    Grid.Row="6"
+                    Grid.Column="5"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+                
+                <TextBox
+                    x:Name="CompletedTotalPercent"
+                    Grid.Row="6"
+                    Grid.Column="6"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+
+                <TextBox
+                    x:Name="PreviousContract"
+                    Grid.Row="2"
+                    Grid.Column="8"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="PreviousContractPercent"
+                    Grid.Row="2"
+                    Grid.Column="9"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="PreviousVariations"
+                    Grid.Row="3"
+                    Grid.Column="8"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                                
+                <TextBox
+                    x:Name="PreviousVariationsPercent"
+                    Grid.Row="3"
+                    Grid.Column="9"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="PreviousSubTotal"
+                    Grid.Row="4"
+                    Grid.Column="8"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                                
+                <TextBox
+                    x:Name="PreviousSubTotalPercent"
+                    Grid.Row="4"
+                    Grid.Column="9"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="PreviousRetention"
+                    Grid.Row="5"
+                    Grid.Column="8"
+                    Margin="5,5,0,0" 
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="PreviousRetentionPercent"
+                    Grid.Row="5"
+                    Grid.Column="9"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="PreviousTotal"
+                    Grid.Row="6"
+                    Grid.Column="8"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+                
+                <TextBox
+                    x:Name="PreviousTotalPercent"
+                    Grid.Row="6"
+                    Grid.Column="9"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+
+                <TextBox
+                    x:Name="CurrentContract"
+                    Grid.Row="2"
+                    Grid.Column="11"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CurrentContractPercent"
+                    Grid.Row="2"
+                    Grid.Column="12"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CurrentVariations"
+                    Grid.Row="3"
+                    Grid.Column="11"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                                
+                <TextBox
+                    x:Name="CurrentVariationsPercent"
+                    Grid.Row="3"
+                    Grid.Column="12"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CurrentSubTotal"
+                    Grid.Row="4"
+                    Grid.Column="11"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CurrentSubTotalPercent"
+                    Grid.Row="4"
+                    Grid.Column="12"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+
+                <TextBox
+                    Grid.Row="5"
+                    Grid.Column="11"
+                    x:Name="CurrentRetention"
+                    DockPanel.Dock="Left" 
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CurrentRetentionPercent"
+                    Grid.Row="5"
+                    Grid.Column="12"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center"/>
+                
+                <TextBox
+                    x:Name="CurrentTotal"
+                    Grid.Row="6"
+                    Grid.Column="11"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+                
+                <TextBox
+                    x:Name="CurrentTotalPercent"
+                    Grid.Row="6"
+                    Grid.Column="12"
+                    Margin="5,5,0,0"
+                    IsReadOnly="True"
+                    Text="0.00"
+                    HorizontalContentAlignment="Center"
+                    VerticalContentAlignment="Center" />
+
+                
+            </Grid>
+           
+        </Border>
+        
+        <DockPanel
+            Grid.Row="2"
+            Grid.Column="0" 
+            LastChildFill="False"
+            Margin="0,5,0,0">
+            <Button 
+                DockPanel.Dock="Right" 
+                Width="80" 
+                Height="40" 
+                Margin="5,0,0,0" 
+                Content="Cancel" 
+                Click="Cancel_Click" />
+            <Button 
+                DockPanel.Dock="Right" 
+                Width="80" 
+                Height="40" 
+                Margin="5,0,0,0" 
+                Content="OK" 
+                Click="OK_Click" />
+            
+        </DockPanel>
+    </Grid>
+</Window>

+ 101 - 0
prs.desktop/Panels/Invoices/ProgressClaimWindow.xaml.cs

@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using Comal.Classes;
+using InABox.Core;
+using InABox.DynamicGrid;
+
+namespace PRSDesktop;
+
+public partial class ProgressClaimWindow : Window
+{
+    public Guid JobID
+    {
+        get => Grid.JobID;
+        set => Grid.JobID = value;
+    }
+    
+    public Guid InvoiceID
+    {
+        get => Grid.InvoiceID;
+        set => Grid.InvoiceID = value;
+    }
+
+    public List<ProgressClaim> Items => Grid.Items;
+
+    public double Retained => Grid.CurrentRetention;
+    
+    public ProgressClaimWindow(Guid jobid, Guid invoiceid)
+    {
+        InitializeComponent();
+
+        JobID = jobid;
+        InvoiceID = invoiceid;
+        
+        Grid.Refresh(true, true);
+        
+    }
+    
+    private void Cancel_Click(object sender, RoutedEventArgs e)
+    {
+        DialogResult = false;
+    }
+
+    private void OK_Click(object sender, RoutedEventArgs e)
+    {
+        DialogResult = true;
+    }
+
+    private void Grid_OnChanged(object? sender, EventArgs e)
+    {
+        UpdateTotals();
+    }
+
+    private void Grid_OnAfterRefresh(object sender, AfterRefreshEventArgs args)
+    {
+        UpdateTotals();
+    }
+
+    private void UpdateTotals()
+    {
+        ProjectContract.Text = $"{Grid.ProjectContract:F2}";
+        ProjectVariations.Text = $"{Grid.ProjectVariations:F2}";
+        ProjectSubTotal.Text = $"{Grid.ProjectSubTotal:F2}";
+        ProjectRetention.Text = $"{Grid.ProjectRetention:F2}";
+        ProjectRetentionPercent.Text = $"{Grid.ProjectRetentionPercent:F2}";
+        ProjectTotal.Text = $"{Grid.ProjectTotal:F2}";
+        
+        CompletedContract.Text = $"{Grid.CompletedContract:F2}";
+        CompletedContractPercent.Text = $"{Grid.CompletedContractPercent:F2}";
+        CompletedVariations.Text = $"{Grid.CompletedVariations:F2}";
+        CompletedVariationsPercent.Text = $"{Grid.CompletedVariationsPercent:F2}";
+        CompletedSubTotal.Text = $"{Grid.CompletedSubTotal:F2}";
+        CompletedSubTotalPercent.Text = $"{Grid.CompletedSubTotalPercent:F2}";
+        CompletedRetention.Text = $"{Grid.CompletedRetention:F2}";
+        CompletedRetentionPercent.Text = $"{Grid.CompletedRetentionPercent:F2}";
+        CompletedTotal.Text = $"{Grid.CompletedTotal:F2}";
+        CompletedTotalPercent.Text = $"{Grid.CompletedTotalPercent:F2}";
+        
+        PreviousContract.Text = $"{Grid.PreviousContract:F2}";
+        PreviousContractPercent.Text = $"{Grid.PreviousContractPercent:F2}";
+        PreviousVariations.Text = $"{Grid.PreviousVariations:F2}";
+        PreviousVariationsPercent.Text = $"{Grid.PreviousVariationsPercent:F2}";
+        PreviousSubTotal.Text = $"{Grid.PreviousSubTotal:F2}";
+        PreviousSubTotalPercent.Text = $"{Grid.PreviousSubTotalPercent:F2}";
+        PreviousRetention.Text = $"{Grid.PreviousRetention:F2}";
+        PreviousRetentionPercent.Text = $"{Grid.PreviousRetentionPercent:F2}";
+        PreviousTotal.Text = $"{Grid.PreviousTotal:F2}";
+        PreviousTotalPercent.Text = $"{Grid.PreviousTotalPercent:F2}";
+        
+        CurrentContract.Text = $"{Grid.CurrentContract:F2}";
+        CurrentContractPercent.Text = $"{Grid.CurrentContractPercent:F2}";
+        CurrentVariations.Text = $"{Grid.CurrentVariations:F2}";
+        CurrentVariationsPercent.Text = $"{Grid.CurrentVariationsPercent:F2}";
+        CurrentSubTotal.Text = $"{Grid.CurrentSubTotal:F2}";
+        CurrentSubTotalPercent.Text = $"{Grid.CurrentSubTotalPercent:F2}";
+        CurrentRetention.Text = $"{Grid.CurrentRetention:F2}";
+        CurrentRetentionPercent.Text = $"{Grid.CurrentRetentionPercent:F2}";
+        CurrentTotal.Text = $"{Grid.CurrentTotal:F2}";
+        CurrentTotalPercent.Text = $"{Grid.CurrentTotalPercent:F2}";
+    }
+}

+ 6 - 7
prs.desktop/Panels/Jobs/DocumentSets/JobDocumentSetFolderTree.cs

@@ -24,7 +24,12 @@ public class JobDocumentSetFolderTree : DynamicDataGrid<JobDocumentSetFolder>, I
         : new Filter<JobDocumentSetFolder>().None();
     
     public bool HasAllItems { get; set; } = true;
-
+    
+    public JobDocumentSetFolderTree() : base()
+    {
+        HiddenColumns.Add(x => x.Documents);
+    }
+    
     private DynamicGridTreeUIComponent<JobDocumentSetFolder>? _uiComponent;
     private DynamicGridTreeUIComponent<JobDocumentSetFolder> UIComponent
     {
@@ -46,12 +51,6 @@ public class JobDocumentSetFolderTree : DynamicDataGrid<JobDocumentSetFolder>, I
             return _uiComponent;
         }
     }
-    
-    public JobDocumentSetFolderTree() : base()
-    {
-        HiddenColumns.Add(x => x.Documents);
-    }
-
     protected override IDynamicGridUIComponent<JobDocumentSetFolder> CreateUIComponent()
     {
         return UIComponent;

+ 25 - 0
prs.desktop/Panels/Jobs/JobScopes/JobScopeGrid.cs

@@ -110,5 +110,30 @@ namespace PRSDesktop
             base.Reload(criteria, columns, ref sort, token, action);
         }
         
+        private DynamicGridTreeUIComponent<JobScope>? _uiComponent;
+        private DynamicGridTreeUIComponent<JobScope> UIComponent
+        {
+            get
+            {
+                if(_uiComponent is null)
+                {
+                    _uiComponent = new DynamicGridTreeUIComponent<JobScope>(
+                        x => x.ID,
+                        x => x.Parent.ID)
+                    {
+                        Parent = this,
+                        MaxRowHeight = 30,
+                    };
+
+                    //_uiComponent.OnContextMenuOpening += JobDocumentSetFolderTree_OnContextMenuOpening;
+                }
+                return _uiComponent;
+            }
+        }
+        protected override IDynamicGridUIComponent<JobScope> CreateUIComponent()
+        {
+            return UIComponent;
+        }
+        
     }
 }

+ 1 - 0
prs.desktop/Panels/Jobs/ProjectsPanel.cs

@@ -210,6 +210,7 @@ public class ProjectsPanel : MasterDetailPanel<Job,ProjectsGrid,ProjectsPanelSet
         ProjectSetupActions.JobStatuses(host);
         ProjectSetupActions.DocumentMilestones(host);
         ProjectSetupActions.JobScopeStatuses(host);
+        ProjectSetupActions.JobRetentionTypes(host);
         ProjectSetupActions.DrawingTemplates(host);
         SystemSetupActions.ERPStatuses(host);
         host.CreateSetupSeparator();

+ 0 - 0
prs.desktop/Panels/Jobs/Retentions/JobRetention.cs


+ 9 - 0
prs.desktop/Setups/ProjectSetupActions.cs

@@ -35,6 +35,15 @@ public static class ProjectSetupActions
             list.ShowDialog();
         });
     }
+    
+    public static void JobRetentionTypes(IPanelHost host)
+    {
+        host.CreateSetupActionIfCanView<JobRetentionType>("Job Retention Types", PRSDesktop.Resources.view, (action) =>
+        {
+            var list = new MasterList(typeof(JobRetentionType));
+            list.ShowDialog();
+        });
+    }
     public static void DrawingTemplates(IPanelHost host)
     {
         host.CreateSetupAction("Drawing Templates", PRSDesktop.Resources.doc_misc, (action) =>