Sfoglia il codice sorgente

Added BillLines to ProgressClaim

Kenric Nugteren 2 settimane fa
parent
commit
c0b1cb44c4

+ 23 - 15
prs.classes/Entities/Bill/BillLine.cs

@@ -34,7 +34,7 @@ namespace Comal.Classes
     [UserTracking(typeof(Bill))]
     public class BillLine : Entity, IPersistent, IRemotable,
         IOneToMany<Bill>, ITaxable, ILicense<AccountsPayableLicense>, IPostableFragment<Bill>, IEntityLookup<BillLine, BillLineLookups>,
-        IInvoiceable
+        IInvoiceable, IJobScopedItem
     {
         [RequiredColumn]
         [EntityRelationship(DeleteAction.Cascade)]
@@ -105,42 +105,46 @@ namespace Comal.Classes
         [EditorSequence(4)]
         public JobLink Job { get; set; }
         
-        [MemoEditor]
+        [LookupDefinition(typeof(BaseJobScopeLookup<BillLine>))]
         [EditorSequence(5)]
+        public JobScopeLink JobScope { get; set; }
+        
+        [MemoEditor]
+        [EditorSequence(6)]
         public string Description { get; set; }
         
         [CurrencyEditor(Summary=Summary.Sum)]
-        [EditorSequence(6)]
+        [EditorSequence(7)]
         public decimal ForeignCurrencyCost { get; set; }
 
         [CurrencyEditor(Summary = Summary.Sum)]
-        [EditorSequence(7)]
+        [EditorSequence(8)]
         public decimal ExTax { get; set; }
 
         [RequiredColumn]
-        [EditorSequence(8)]
+        [EditorSequence(9)]
         public TaxCodeLink TaxCode { get; set; }
 
         
         [CurrencyEditor(Summary = Summary.Sum)]
-        [EditorSequence(9)]
+        [EditorSequence(10)]
         public decimal Tax { get; set; }
 
         [CurrencyEditor(Summary = Summary.Sum)]
-        [EditorSequence(10)]
+        [EditorSequence(11)]
         public decimal IncTax { get; set; }
 
-        [EditorSequence(11)]
+        [EditorSequence(12)]
         public PurchaseGLCodeLink PurchaseGL { get; set; }
         
-        [EditorSequence(12)]
+        [EditorSequence(13)]
         public CostCentreLink CostCentre { get; set; }
         
-        [EditorSequence(13)]
+        [EditorSequence(14)]
         [Editable(Editable.Disabled)]
         public InvoiceLink Invoice { get; set; }
 
-        [EditorSequence(14)]
+        [EditorSequence(15)]
         public ActualCharge Charge { get; set; }
         
         [NullEditor]
@@ -161,15 +165,15 @@ namespace Comal.Classes
             LinkedProperties.Register<BillLine, CostCentreLink, Guid>(x => x.OrderItem.CostCentre, x => x.ID,
                 x => x.CostCentre.ID);
             LinkedProperties.Register<BillLine, TaxCodeLink, Guid>(x => x.OrderItem.TaxCode, x => x.ID,
-                x => x.TaxCode.ID);            
+                x => x.TaxCode.ID);
             LinkedProperties.Register<BillLine, TaxCodeLink, String>(x => x.OrderItem.TaxCode, x => x.Code,
-                x => x.TaxCode.Code);            
+                x => x.TaxCode.Code);
 
             LinkedProperties.Register<BillLine, TaxCodeLink, String>(x => x.OrderItem.TaxCode, x => x.Description,
-                x => x.TaxCode.Description);            
+                x => x.TaxCode.Description);
 
             LinkedProperties.Register<BillLine, TaxCodeLink, decimal>(x => x.OrderItem.TaxCode, x => x.Rate,
-                x => x.TaxCode.Rate);            
+                x => x.TaxCode.Rate);
             
             LinkedProperties.Register<BillLine, PurchaseOrderItemLink, decimal>(x => x.OrderItem, x => x.Tax,
                 x => x.Tax);
@@ -177,6 +181,8 @@ namespace Comal.Classes
             LinkedProperties.Register<BillLine, PurchaseOrderItemLink, decimal>(x => x.OrderItem, x => x.IncTax,
                 x => x.IncTax);
 
+            Classes.JobScope.LinkScopeProperties<BillLine>();
+
             LinkedProperties.Register<BillLine, TaxCodeLink, decimal>(x => x.TaxCode, x => x.Rate, x => x.TaxRate);
 
             LinkedProperties.Register<BillLine, PurchaseGLCodeLink, Guid>(x => x.Product.PurchaseGL, x => x.ID, x => x.PurchaseGL.ID);
@@ -211,6 +217,8 @@ namespace Comal.Classes
                     Product.Clear();
                     Job.ID = Guid.Empty;
                     Job.Clear();
+                    JobScope.ID = Guid.Empty;
+                    JobScope.Clear();
                 }
                 else if((ProductColumn.IsEqualTo(name) && after is Guid productID && productID != Guid.Empty)
                         || (JobColumn.IsEqualTo(name) && after is Guid jobID && jobID != Guid.Empty))

+ 15 - 0
prs.classes/Entities/Job/JobScopes/JobScope.cs

@@ -397,4 +397,19 @@ namespace Comal.Classes
             LinkedProperties.Register<TScoped, JobScopeLink, String>(ass => ass.Job.DefaultScope, scope => scope.Description, ass => ass.JobScope.Description);
         }
     }
+
+    public class BaseJobScopeLookup<T> : LookupDefinitionGenerator<JobScope, T>
+        where T : Entity, IJobScopedItem
+    {
+        public override Filter<JobScope> DefineFilter(T[] items)
+        {
+            var item = items?.Length == 1 ? items[0] : null;
+            if (item != null)
+                return Filter<JobScope>.Where(x => x.Job.ID).IsEqualTo(item.Job.ID).And(x => x.Status.Approved).IsEqualTo(true);
+            return Filter.None<JobScope>();
+        }
+        
+        public override Columns<T> DefineFilterColumns()
+            => Columns.None<T>().Add(x => x.Job.ID);
+    }
 }

+ 7 - 1
prs.classes/Entities/PurchaseOrder/PurchaseOrderItem.cs

@@ -11,7 +11,7 @@ namespace Comal.Classes
     [UserTracking(typeof(Bill))]
     [Caption("Purchase Order Items")]
     public class PurchaseOrderItem : StockEntity, IRemotable, IPersistent, IOneToMany<PurchaseOrder>, ITaxable, IOneToMany<Consignment>, IOneToMany<Job>,
-        ILicense<AccountsPayableLicense>, IPostableFragment<PurchaseOrder>, IJobMaterial
+        ILicense<AccountsPayableLicense>, IPostableFragment<PurchaseOrder>, IJobMaterial, IJobScopedItem
     {
         
         [RequiredColumn]
@@ -47,6 +47,11 @@ namespace Comal.Classes
         [EntityRelationship(DeleteAction.SetNull)]
         [EditorSequence(4)]
         public JobLink Job { get; set; }
+        
+        [EntityRelationship(DeleteAction.SetNull)]
+        [EditorSequence(5)]
+        [LookupDefinition(typeof(BaseJobScopeLookup<PurchaseOrderItem>))]
+        public JobScopeLink JobScope { get; set; }
 
         [MemoEditor(Visible = Visible.Default)]
         [EditorSequence(6)]
@@ -241,6 +246,7 @@ namespace Comal.Classes
                 x => x.Cost);
 
             StockEntity.LinkStockDimensions<PurchaseOrderItem>();
+            Classes.JobScope.LinkScopeProperties<PurchaseOrderItem>();
             
             DefaultColumns.Add<PurchaseOrderItem>(x => x.PurchaseOrder.PONumber, caption: "PONumber");
             DefaultColumns.Add<PurchaseOrderItem>(x => x.Description);

+ 2 - 0
prs.classes/Entities/PurchaseOrder/PurchaseOrderItemLink.cs

@@ -10,6 +10,8 @@ namespace Comal.Classes
         public override Guid ID { get; set; }
 
         public JobLink Job { get; set; }
+
+        public JobScopeLink JobScope { get; set; }
         
         //[TextBoxEditor(Visible=Visible.Optional,Editable = Editable.Hidden)]
         //public string Allocations { get; set; }

+ 24 - 6
prs.desktop/Panels/Invoices/InvoiceGrid.cs

@@ -15,6 +15,7 @@ using InABox.Configuration;
 using System.Collections.Generic;
 using System.Threading;
 using PRSDesktop;
+using System.Threading.Tasks;
 
 namespace PRSDesktop
 {
@@ -126,21 +127,38 @@ namespace PRSDesktop
                         new KeyedQueryDef<Assignment>(
                             Filter<Assignment>.Where(x => x.Job.ID).IsEqualTo(Master.ID)
                                 .And(x => x.Invoice.ID).IsEqualTo(Guid.Empty),
-                            Columns.Required<Assignment>().Add(x => x.Invoice.ID)));
-
-                    var movements = results.GetObjects<StockMovement>().ToList();
+                            Columns.Required<Assignment>().Add(x => x.Invoice.ID)),
+                        new KeyedQueryDef<BillLine>(
+                            Filter<BillLine>.And(
+                                ProgressClaimGrid.BillFilter(Filter<BillLine>.Where(x => x.Job.ID).IsEqualTo(Master.ID)
+                                    .Or(x => x.OrderItem.Job.ID).IsEqualTo(Master.ID)),
+                                Filter<BillLine>.Where(x => x.Invoice.ID).IsEqualTo(Guid.Empty)),
+                            Columns.Required<BillLine>()
+                                .Add(x => x.ID)
+                                .Add(x => x.Invoice.ID)));
+
+                    var movements = results.GetList<StockMovement>();
                     foreach(var mvt in movements)
                     {
                         mvt.Invoice.ID = invoice.ID;
                     }
-                    Client.Save(movements, "Attached to new progress claim.");
+                    var movementTask = Client.SaveAsync(movements, "Attached to new progress claim.");
 
-                    var assignments = results.GetObjects<Assignment>().ToList();
+                    var assignments = results.GetList<Assignment>();
                     foreach(var ass in assignments)
                     {
                         ass.Invoice.ID = invoice.ID;
                     }
-                    Client.Save(assignments, "Attached to new progress claim.");
+                    var assignmentTask = Client.SaveAsync(assignments, "Attached to new progress claim.");
+
+                    var billLines = results.GetList<BillLine>();
+                    foreach(var bill in billLines)
+                    {
+                        bill.Invoice.ID = invoice.ID;
+                    }
+                    var billLineTask = Client.SaveAsync(billLines, "Attached to new progress claim.");
+
+                    Task.WaitAll(movementTask, assignmentTask, billLineTask);
 
                     Refresh(false, true);
                 }

+ 93 - 0
prs.desktop/Panels/Invoices/ProgressClaim/ProgressClaimExpensesGrid.cs

@@ -0,0 +1,93 @@
+using Comal.Classes;
+using InABox.Core;
+using InABox.DynamicGrid;
+using InABox.WPF;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Media;
+
+namespace PRSDesktop.Panels.Invoices;
+
+internal class ProgressClaimExpensesGrid : DynamicDataGrid<BillLine>, ISpecificGrid
+{
+    public ProgressClaim Claim { get; set; }
+
+    public Invoice? Invoice { get; set; }
+
+    public Job Job { get; set; }
+
+    public ProgressClaimExpensesGrid(ProgressClaim claim, Invoice? invoice, Job job)
+    {
+        Claim = claim;
+        Invoice = invoice;
+        Job = job;
+    }
+
+    protected override void Init()
+    {
+        base.Init();
+
+        HiddenColumns.Add(x => x.Invoice.ID);
+
+        ColumnsTag = $"{nameof(ProgressClaim)}.Labour";
+    }
+
+    protected override IDynamicGridUIComponent<BillLine> CreateUIComponent()
+    {
+        return new UIComponent(this);
+    }
+    private class UIComponent : DynamicGridGridUIComponent<BillLine>
+    {
+        private ProgressClaimExpensesGrid Grid;
+
+        public UIComponent(ProgressClaimExpensesGrid grid)
+        {
+            Parent = grid;
+            Grid = grid;
+        }
+
+        protected override Brush? GetCellBackground(CoreRow row, DynamicColumnBase column)
+        {
+            var invoiceID = row.Get<BillLine, Guid>(x => x.Invoice.ID);
+            if(invoiceID == (Grid.Invoice?.ID ?? Guid.Empty))
+            {
+                return Colors.LightGreen.ToBrush(0.5);
+            }
+            else
+            {
+                return Colors.Silver.ToBrush(0.5);
+            }
+        }
+    }
+
+    protected override void DoReconfigure(DynamicGridOptions options)
+    {
+        base.DoReconfigure(options);
+
+        options.FilterRows = true;
+        options.SelectColumns = true;
+    }
+
+    public override DynamicGridColumns GenerateColumns()
+    {
+        return base.GenerateColumns();
+    }
+
+    protected override void Reload(Filters<BillLine> criteria, Columns<BillLine> columns, ref SortOrder<BillLine>? sort, CancellationToken token, Action<CoreTable?, Exception?> action)
+    {
+        var jobFilter = Filter<BillLine>.Or(
+            Filter<BillLine>.Where(x => x.OrderItem.ID).IsEqualTo(Guid.Empty)
+                .And(x => x.Job.ID).IsEqualTo(Job.ID),
+            Filter<BillLine>.Where(x => x.OrderItem.ID).IsNotEqualTo(Guid.Empty)
+                .And(x => x.OrderItem.Job.ID).IsEqualTo(Job.ID));
+        criteria.Add(Filter<BillLine>.And(
+            ProgressClaimGrid.BillFilter(jobFilter),
+            Filter<BillLine>.Where(x => x.JobScope.ID).IsEqualTo(Claim.JobScope.ID)
+                .Or(x => x.OrderItem.JobScope.ID).IsEqualTo(Claim.JobScope.ID)));
+        base.Reload(criteria, columns, ref sort, token, action);
+    }
+}

+ 43 - 2
prs.desktop/Panels/Invoices/ProgressClaim/ProgressClaimGrid.cs

@@ -154,9 +154,23 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
                 columns)
             .Add(
                 Filter<Assignment>.Where(x => x.Job.ID).IsEqualTo(JobID),
-                Columns.None<Assignment>().Add(x => x.JobScope.ID)
+                Columns.None<Assignment>()
+                    .Add(x => x.JobScope.ID)
                     .Add(x => x.Actual.Duration)
                     .Add(x => x.Employee.HourlyRate))
+            .Add(
+                Filter<BillLine>.And(
+                    // Either the bill or the linked order item attached to this job.
+                    BillFilter(Filter<BillLine>.Where(x => x.Job.ID).IsEqualTo(JobID)
+                        .Or(x => x.OrderItem.Job.ID).IsEqualTo(JobID)),
+                    Filter<BillLine>.Where(x => x.JobScope.ID).IsNotEqualTo(Guid.Empty)
+                        .Or(x => x.OrderItem.JobScope.ID).IsNotEqualTo(Guid.Empty)),
+                Columns.None<BillLine>()
+                    .Add(x => x.ID)
+                    .Add(x => x.ExTax)
+                    .Add(x => x.JobScope.ID)
+                    .Add(x => x.OrderItem.ID)
+                    .Add(x => x.OrderItem.JobScope.ID))
             .Add(
                 Filter<Invoice>.Where(x => x.Job.ID).IsEqualTo(JobID),
                 Columns.None<Invoice>()
@@ -183,6 +197,9 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
         var assignments = results.GetObjects<Assignment>()
             .GroupBy(x => x.JobScope.ID)
             .ToDictionary(x => x.Key, x => x.Sum(x => x.Actual.Duration.TotalHoursDecimal() * x.Employee.HourlyRate));
+        var billLines = results.GetObjects<BillLine>()
+            .GroupBy(x => x.OrderItem.ID != Guid.Empty ? x.OrderItem.JobScope.ID : x.JobScope.ID)
+            .ToDictionary(x => x.Key, x => x.Sum(x => x.ExTax));
 
         Invoices = results.GetArray<Invoice>();
         
@@ -198,7 +215,7 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
 
             var prev = Invoice.ID == Guid.Empty
                 ? InvoiceLines.Where(x => x.Scope.ID == scope.ID)
-                : InvoiceLines.Where(x => x.Scope.ID == scope.ID && x.Invoice.Date < Invoice.Date);
+                : InvoiceLines.Where(x => x.Scope.ID == scope.ID && (x.Invoice.Date < Invoice.Date || x.Invoice.Date == Invoice.Date && x.Invoice.Number < Invoice.Number));
                 
             var current = InvoiceLines.Where(x => x.Scope.ID == scope.ID && x.Invoice.ID == Invoice.ID);
             
@@ -215,6 +232,7 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
             
             newItem.Materials = scope.MaterialsExTax;
             newItem.Labour = assignments.GetValueOrDefault(scope.ID);
+            newItem.Expenses = billLines.GetValueOrDefault(scope.ID);
             newItem.GLCodeID = Job.Account.ID != Guid.Empty
                 ? Job.Account.GLCode.ID
                 : Job.Customer.GLCode.ID;
@@ -227,6 +245,21 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
         _loadedData = true;
     }
 
+    public static Filter<BillLine> BillFilter(Filter<BillLine> jobFilter)
+    {
+        return Filter<BillLine>.Or(
+            jobFilter,
+            // Not attached to anything
+            Filter<BillLine>.Where(x => x.Product.ID).IsEqualTo(Guid.Empty)
+                .And(x => x.OrderItem.ID).IsEqualTo(Guid.Empty),
+            // Attached to a product
+            Filter<BillLine>.Where(x => x.Product.ID).IsNotEqualTo(Guid.Empty)
+                .And(x => x.Product.NonStock).IsEqualTo(true),
+            // Attached to a non-stock purchase-order
+            Filter<BillLine>.Where(x => x.OrderItem.ID).IsNotEqualTo(Guid.Empty)
+                .And(x => x.OrderItem.Product.NonStock).IsEqualTo(true));
+    }
+
     protected override void Changed()
     {
         base.Changed();
@@ -367,6 +400,13 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
                 var window = DynamicGridUtils.CreateGridWindow($"Labour", grid);
                 window.ShowDialog();
             }
+            else if(gridColumn.ColumnName.EqualsProperty<ProgressClaim>(x => x.Expenses))
+            {
+                var grid = new ProgressClaimExpensesGrid(claim, Invoice, Job);
+
+                var window = DynamicGridUtils.CreateGridWindow($"Expenses", grid);
+                window.ShowDialog();
+            }
         }
     }
 
@@ -378,6 +418,7 @@ public class ProgressClaimGrid : DynamicItemsListGrid<ProgressClaim>
         columns.Add<ProgressClaim>(x => x.JobScope.Description, 0, "Description", "", Alignment.MiddleCenter);
         columns.Add<ProgressClaim>(x => x.Materials, 80, "Material $", "", Alignment.MiddleCenter);
         columns.Add<ProgressClaim>(x => x.Labour, 80, "Labour $", "", Alignment.MiddleCenter);
+        columns.Add<ProgressClaim>(x => x.Expenses, 80, "Expenses $", "", Alignment.MiddleCenter);
         columns.Add<ProgressClaim>(x => x.JobScope.ExTax, 100, "Quote $", "", Alignment.MiddleCenter);
         columns.Add<ProgressClaim>(x => x.PreviouslyClaimed, 100, "Prev $", "C2", Alignment.MiddleCenter);
         columns.Add<ProgressClaim>(x => x.PreviouslyClaimedPercent, 80, "Prev %", "", Alignment.MiddleCenter);

+ 7 - 2
prs.desktop/Panels/PurchaseOrders/SupplierPurchaseOrderItemOneToMany.cs

@@ -822,8 +822,13 @@ public class SupplierPurchaseOrderItemOneToMany : DynamicOneToManyGrid<PurchaseO
         object value)
     {
         var results = base.EditorValueChanged(editor, items, name, value);
-        if (name.Equals("Product.TaxCode.ID"))
-            DynamicGridUtils.UpdateEditorValue(items, "TaxCode.ID", (Guid)value, results);
+        if (name.EqualsProperty<PurchaseOrderItem>(x => x.Product.TaxCode.ID))
+            DynamicGridUtils.UpdateEditorValue(items, x => x.TaxCode.ID, (Guid)value, results);
+        if(name.EqualsProperty<PurchaseOrderItem>(x => x.Job.ID))
+        {
+            if (editor.TryFindEditor<PurchaseOrderItem, ILookupEditorControl>(x => x.JobScope.ID, out var scope))
+                DefineLookups(scope, items, false); 
+        }
         return results;
     }
 }

+ 13 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBillLineGrid.cs

@@ -307,6 +307,17 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
         }
     }
 
+    protected override Dictionary<string, object?> EditorValueChanged(IDynamicEditorForm editor, BillLine[] items, string name, object value)
+    {
+        var result = base.EditorValueChanged(editor, items, name, value);
+        if(name.EqualsProperty<BillLine>(x => x.Job.ID))
+        {
+            if (editor.TryFindEditor<BillLine, ILookupEditorControl>(x => x.JobScope.ID, out var scope))
+                DefineLookups(scope, items, false); 
+        }
+        return result;
+    }
+
     protected override void DoReconfigure(DynamicGridOptions options)
     {
         base.DoReconfigure(options);
@@ -546,6 +557,8 @@ public class SupplierBillLineGrid : DynamicOneToManyGrid<Bill, BillLine>
                         .Add(x => x.TaxCode.Code)
                         .Add(x => x.TaxCode.Description)
                         .Add(x => x.TaxCode.Rate)
+                        .Add(x => x.Job.ID)
+                        .Add(x => x.JobScope.ID)
                         .Add(x => x.Qty)
                         .Add(x => x.ForeignCurrencyCost)
                         .Add(x => x.TaxRate)

+ 2 - 0
prs.desktop/Panels/Suppliers/Bills/SupplierBillPanel.xaml.cs

@@ -298,6 +298,8 @@ public partial class SupplierBillPanel : UserControl, IPanel<Bill>, IPropertiesP
 
     private void Bill_OnOnCancel()
     {
+        Bill.CancelItem();
+
         CancelApproval();
         ReloadBills();
         SetChanged(false);