|
|
@@ -1,281 +1,388 @@
|
|
|
using Comal.Classes;
|
|
|
using InABox.Clients;
|
|
|
using InABox.Core;
|
|
|
+using PRSDimensionUtils;
|
|
|
|
|
|
-namespace PRS.Shared
|
|
|
+namespace PRS.Shared;
|
|
|
+
|
|
|
+
|
|
|
+public enum InvoiceTimeCalculation
|
|
|
{
|
|
|
+ Detailed,
|
|
|
+ Activity,
|
|
|
+ Collapsed,
|
|
|
+}
|
|
|
|
|
|
- public enum InvoiceTimeCalculation
|
|
|
- {
|
|
|
- Detailed,
|
|
|
- Activity,
|
|
|
- Collapsed,
|
|
|
- }
|
|
|
-
|
|
|
- public enum InvoiceMaterialCalculation
|
|
|
+public enum InvoiceMaterialCalculation
|
|
|
+{
|
|
|
+ Detailed,
|
|
|
+ Product,
|
|
|
+ CostCentre,
|
|
|
+ Collapsed,
|
|
|
+}
|
|
|
+
|
|
|
+public enum InvoiceExpensesCalculation
|
|
|
+{
|
|
|
+ Detailed,
|
|
|
+ Collapsed,
|
|
|
+}
|
|
|
+
|
|
|
+public static class InvoiceUtilities
|
|
|
+{
|
|
|
+
|
|
|
+ private class InvoiceLineDetail
|
|
|
{
|
|
|
- Detailed,
|
|
|
- Product,
|
|
|
- CostCentre,
|
|
|
- Collapsed,
|
|
|
+ public String Description { get; set; }
|
|
|
+ public TaxCodeLink TaxCode { get; set; }
|
|
|
+ public double Quantity { get; set; }
|
|
|
+ public double Charge { get; set; }
|
|
|
+
|
|
|
+ public InvoiceLineDetail()
|
|
|
+ {
|
|
|
+ TaxCode = new TaxCodeLink();
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- public static class InvoiceUtilities
|
|
|
+
|
|
|
+ private static async Task<InvoiceLine[]> TimeLines(Invoice invoice, InvoiceTimeCalculation timesummary)
|
|
|
{
|
|
|
-
|
|
|
- private class InvoiceLineDetail
|
|
|
+ var timelines = new Dictionary<Guid, InvoiceLineDetail>();
|
|
|
+
|
|
|
+ var activitiesTask = Task.Run(() =>
|
|
|
+ {
|
|
|
+ return Client.Query(
|
|
|
+ Filter<CustomerActivitySummary>.Where(x => x.Customer.ID).InList(invoice.CustomerLink.ID, Guid.Empty),
|
|
|
+ Columns.None<CustomerActivitySummary>().Add(x => x.Customer.ID)
|
|
|
+ .Add(x => x.Activity.ID)
|
|
|
+ .Add(x => x.Activity.Code)
|
|
|
+ .Add(x => x.Activity.Description)
|
|
|
+ .Add(x => x.Charge.TaxCode.ID)
|
|
|
+ .Add(x => x.Charge.TaxCode.Rate)
|
|
|
+ .Add(x => x.Charge.Chargeable)
|
|
|
+ .Add(x => x.Charge.FixedCharge)
|
|
|
+ .Add(x => x.Charge.ChargeRate)
|
|
|
+ .Add(x => x.Charge.ChargePeriod)
|
|
|
+ .Add(x => x.Charge.MinimumCharge))
|
|
|
+ .ToObjects<CustomerActivitySummary>()
|
|
|
+ .GroupByDictionary(x => (CustomerID: x.Customer.ID, ActivityID: x.Activity.ID));
|
|
|
+ });
|
|
|
+ var assignmentsTask = Task.Run(() =>
|
|
|
{
|
|
|
- public Guid ID { get; set; }
|
|
|
- public String Description { get; set; }
|
|
|
- public TaxCodeLink TaxCode { get; set; }
|
|
|
- public double Quantity { get; set; }
|
|
|
- public double Charge { get; set; }
|
|
|
+ return Client.Query(
|
|
|
+ Filter<Assignment>.Where(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
|
|
|
+ Columns.None<Assignment>()
|
|
|
+ .Add(x => x.ID)
|
|
|
+ .Add(x => x.ActivityLink.ID)
|
|
|
+ .Add(x => x.ActivityLink.Description)
|
|
|
+ .Add(x => x.Date)
|
|
|
+ .Add(x => x.Description)
|
|
|
+ .Add(x => x.Charge.OverrideCharge)
|
|
|
+ .Add(x => x.Charge.Charge)
|
|
|
+ .Add(x => x.Charge.OverrideQuantity)
|
|
|
+ .Add(x => x.Charge.Quantity)
|
|
|
+ .Add(x => x.Actual.Duration),
|
|
|
+ new SortOrder<Assignment>(x => x.Date))
|
|
|
+ .ToArray<Assignment>();
|
|
|
+ });
|
|
|
|
|
|
- public InvoiceLineDetail()
|
|
|
- {
|
|
|
- TaxCode = new TaxCodeLink();
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public static void GenerateInvoiceLines(Guid invoiceid, InvoiceTimeCalculation timesummary, InvoiceMaterialCalculation partsummary, IProgress<String>? progress )
|
|
|
+ var activities = await activitiesTask;
|
|
|
+ foreach (var assignment in await assignmentsTask)
|
|
|
{
|
|
|
-
|
|
|
- CustomerActivitySummary[] activities = new CustomerActivitySummary[] { };
|
|
|
- CustomerProductSummary[] products = new CustomerProductSummary[] { };
|
|
|
+ var id = timesummary switch
|
|
|
+ {
|
|
|
+ InvoiceTimeCalculation.Detailed => assignment.ID,
|
|
|
+ InvoiceTimeCalculation.Activity => assignment.ActivityLink.ID,
|
|
|
+ _ => Guid.Empty
|
|
|
+ };
|
|
|
|
|
|
- Assignment[] assignments = new Assignment[] { };
|
|
|
- var movements = Array.Empty<StockMovement>();
|
|
|
+ var description = timesummary switch
|
|
|
+ {
|
|
|
+ InvoiceTimeCalculation.Detailed => string.Format("{0:dd MMM yy} - {1}", assignment.Date, assignment.Description),
|
|
|
+ InvoiceTimeCalculation.Activity => assignment.ActivityLink.Description,
|
|
|
+ _ => "Labour"
|
|
|
+ };
|
|
|
|
|
|
- progress?.Report("Loading Invoice");
|
|
|
- var invoice = new Client<Invoice>().Load(Filter<Invoice>.Where(x => x.ID).IsEqualTo(invoiceid)).FirstOrDefault();
|
|
|
-
|
|
|
- progress?.Report("Loading Detail Data");
|
|
|
- var setup = new Task[]
|
|
|
+ var quantity = assignment.Charge.OverrideQuantity
|
|
|
+ ? TimeSpan.FromHours(assignment.Charge.Quantity)
|
|
|
+ : assignment.Actual.Duration;
|
|
|
+
|
|
|
+ var activity = activities.GetValueOrDefault((invoice.CustomerLink.ID, assignment.ActivityLink.ID))?.FirstOrDefault()
|
|
|
+ ?? activities.GetValueOrDefault((Guid.Empty, assignment.ActivityLink.ID))?.FirstOrDefault()
|
|
|
+ ?? new CustomerActivitySummary();
|
|
|
+
|
|
|
+ double charge;
|
|
|
+ if (assignment.Charge.OverrideCharge)
|
|
|
+ {
|
|
|
+ charge = quantity.TotalHours * assignment.Charge.Charge;
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
+ var fixedcharge = activity.Charge.FixedCharge;
|
|
|
|
|
|
+ var chargeperiod = !activity.Charge.ChargePeriod.Equals(TimeSpan.Zero)
|
|
|
+ ? activity.Charge.ChargePeriod
|
|
|
+ : TimeSpan.FromHours(1);
|
|
|
|
|
|
- Task.Run(() =>
|
|
|
- {
|
|
|
- var oldlines = new Client<InvoiceLine>().Query(
|
|
|
- Filter<InvoiceLine>.Where(x => x.InvoiceLink.ID).IsEqualTo(invoice.ID),
|
|
|
- Columns.None<InvoiceLine>().Add(x => x.ID)
|
|
|
- ).Rows.Select(x => x.ToObject<InvoiceLine>()).ToArray();
|
|
|
- new Client<InvoiceLine>().Delete(oldlines, "");
|
|
|
- }),
|
|
|
-
|
|
|
- Task.Run(() =>
|
|
|
- {
|
|
|
- activities =
|
|
|
- new Client<CustomerActivitySummary>().Query(
|
|
|
- Filter<CustomerActivitySummary>.Where(x => x.Customer.ID).InList(invoice.CustomerLink.ID, Guid.Empty),
|
|
|
- Columns.None<CustomerActivitySummary>().Add(x => x.Customer.ID)
|
|
|
- .Add(x => x.Activity.ID)
|
|
|
- .Add(x => x.Activity.Code)
|
|
|
- .Add(x => x.Activity.Description)
|
|
|
- .Add(x => x.Charge.TaxCode.ID)
|
|
|
- .Add(x => x.Charge.TaxCode.Rate)
|
|
|
- .Add(x => x.Charge.Chargeable)
|
|
|
- .Add(x => x.Charge.FixedCharge)
|
|
|
- .Add(x => x.Charge.ChargeRate)
|
|
|
- .Add(x => x.Charge.ChargePeriod)
|
|
|
- .Add(x => x.Charge.MinimumCharge)
|
|
|
- ).Rows.Select(r => r.ToObject<CustomerActivitySummary>()).ToArray();
|
|
|
- }),
|
|
|
-
|
|
|
- Task.Run(() =>
|
|
|
- {
|
|
|
- assignments = new Client<Assignment>().Query(
|
|
|
- Filter<Assignment>.Where(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
|
|
|
- null,
|
|
|
- new SortOrder<Assignment>(x => x.Date)
|
|
|
- ).Rows.Select(x => x.ToObject<Assignment>()).ToArray();
|
|
|
- }),
|
|
|
-
|
|
|
- Task.Run(() =>
|
|
|
- {
|
|
|
- products =
|
|
|
- new Client<CustomerProductSummary>().Query(
|
|
|
- Filter<CustomerProductSummary>.Where(x => x.Customer.ID).InList(invoice.CustomerLink.ID, Guid.Empty),
|
|
|
- Columns.None<CustomerProductSummary>().Add(x => x.Customer.ID)
|
|
|
- .Add(x => x.Product.ID)
|
|
|
- .Add(x => x.Product.Code)
|
|
|
- .Add(x => x.Product.Name)
|
|
|
- .Add(x => x.Product.TaxCode.ID)
|
|
|
- .Add(x => x.Product.TaxCode.Rate)
|
|
|
- .Add(x => x.Charge.Chargeable)
|
|
|
- .Add(x => x.Charge.PriceType)
|
|
|
- .Add(x => x.Charge.Price)
|
|
|
- .Add(x => x.Charge.Markup)
|
|
|
- ).Rows.Select(r => r.ToObject<CustomerProductSummary>()).ToArray();
|
|
|
- }),
|
|
|
-
|
|
|
- Task.Run(() =>
|
|
|
+ var rounded = quantity.Ceiling(chargeperiod);
|
|
|
+
|
|
|
+ var multiplier = TimeSpan.FromHours(1).TotalHours / chargeperiod.TotalHours;
|
|
|
+ var rate = activity.Charge.ChargeRate * multiplier;
|
|
|
+
|
|
|
+ var mincharge = activity.Charge.MinimumCharge;
|
|
|
+
|
|
|
+ charge = Math.Max(fixedcharge + (rounded.TotalHours * rate), mincharge);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!timelines.TryGetValue(id, out var timeline))
|
|
|
+ {
|
|
|
+ timeline = new InvoiceLineDetail
|
|
|
{
|
|
|
- movements = new Client<StockMovement>().Query(
|
|
|
- Filter<StockMovement>.Where(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
|
|
|
- null
|
|
|
- ).ToArray<StockMovement>();
|
|
|
- })
|
|
|
+ Description = description
|
|
|
+ };
|
|
|
+ timeline.TaxCode.CopyFrom(activity.Charge.TaxCode);
|
|
|
+ timelines.Add(id, timeline);
|
|
|
+ }
|
|
|
+
|
|
|
+ timeline.Quantity += quantity.TotalHours;
|
|
|
+ timeline.Charge += charge;
|
|
|
+ }
|
|
|
+
|
|
|
+ return timelines.Values.ToArray(line =>
|
|
|
+ {
|
|
|
+ var update = new InvoiceLine();
|
|
|
+ update.InvoiceLink.ID = invoice.ID;
|
|
|
+ update.Description = line.Description;
|
|
|
+ update.TaxCode.CopyFrom(line.TaxCode);
|
|
|
+ update.Quantity = timesummary != InvoiceTimeCalculation.Collapsed ? line.Quantity : 1;
|
|
|
+ update.ExTax = line.Charge;
|
|
|
+ return update;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ private static async Task<InvoiceLine[]> PartLines(Invoice invoice, InvoiceMaterialCalculation partsummary)
|
|
|
+ {
|
|
|
+ var productsTask = Task.Run(() =>
|
|
|
+ {
|
|
|
+ return Client.Query(
|
|
|
+ Filter<CustomerProductSummary>.Where(x => x.Customer.ID).InList(invoice.CustomerLink.ID, Guid.Empty),
|
|
|
+ Columns.None<CustomerProductSummary>()
|
|
|
+ .Add(x => x.Customer.ID)
|
|
|
+ .Add(x => x.Product.ID)
|
|
|
+ .Add(x => x.Product.Code)
|
|
|
+ .Add(x => x.Product.Name)
|
|
|
+ .Add(x => x.Product.TaxCode.ID)
|
|
|
+ .Add(x => x.Product.TaxCode.Rate)
|
|
|
+ .Add(x => x.Charge.Chargeable)
|
|
|
+ .Add(x => x.Charge.PriceType)
|
|
|
+ .Add(x => x.Charge.Price)
|
|
|
+ .Add(x => x.Charge.Markup))
|
|
|
+ .ToObjects<CustomerProductSummary>()
|
|
|
+ .GroupByDictionary(x => (CustomerID: x.Customer.ID, ProductID: x.Product.ID));
|
|
|
+ });
|
|
|
+ var movementsTask = Task.Run(() =>
|
|
|
+ {
|
|
|
+ return Client.Query(
|
|
|
+ Filter<StockMovement>.Where(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
|
|
|
+ Columns.None<StockMovement>()
|
|
|
+ .Add(x => x.ID)
|
|
|
+ .Add(x => x.Qty)
|
|
|
+ .Add(x => x.Product.ID)
|
|
|
+ .Add(x => x.Product.Name)
|
|
|
+ .Add(x => x.Product.CostCentre.ID)
|
|
|
+ .Add(x => x.Product.CostCentre.Description)
|
|
|
+ .Add(x => x.Style.Code)
|
|
|
+ .Add(x => x.Dimensions.UnitSize)
|
|
|
+ .Add(x => x.Charge.OverrideCharge)
|
|
|
+ .Add(x => x.Charge.Charge)
|
|
|
+ .Add(x => x.Charge.OverrideQuantity)
|
|
|
+ .Add(x => x.Charge.Quantity))
|
|
|
+ .ToArray<StockMovement>();
|
|
|
+ });
|
|
|
+
|
|
|
+ var partlines = new Dictionary<Guid, InvoiceLineDetail>();
|
|
|
+
|
|
|
+ var products = await productsTask;
|
|
|
+ foreach (var item in await movementsTask)
|
|
|
+ {
|
|
|
+ var id = partsummary switch
|
|
|
+ {
|
|
|
+ InvoiceMaterialCalculation.Detailed => item.ID,
|
|
|
+ InvoiceMaterialCalculation.Product => item.Product.ID,
|
|
|
+ InvoiceMaterialCalculation.CostCentre => item.Product.CostCentre.ID,
|
|
|
+ _ => Guid.Empty
|
|
|
+ };
|
|
|
+
|
|
|
+ var description = partsummary switch
|
|
|
+ {
|
|
|
+ InvoiceMaterialCalculation.Detailed => $"{item.Product.Name}: {item.Style.Code}; {item.Dimensions.UnitSize}",
|
|
|
+ InvoiceMaterialCalculation.Product => item.Product.Name,
|
|
|
+ InvoiceMaterialCalculation.CostCentre => item.Product.CostCentre.Description,
|
|
|
+ _ => "Materials"
|
|
|
};
|
|
|
|
|
|
- Task.WaitAll(setup);
|
|
|
+ var quantity = item.Charge.OverrideQuantity
|
|
|
+ ? item.Charge.Quantity
|
|
|
+ : item.Qty;
|
|
|
|
|
|
- List<InvoiceLine> updates = new List<InvoiceLine>();
|
|
|
-
|
|
|
- progress?.Report("Calculating...");
|
|
|
- var timelines = new List<InvoiceLineDetail>();
|
|
|
+ var product =
|
|
|
+ products.GetValueOrDefault((invoice.CustomerLink.ID, item.Product.ID))?.FirstOrDefault()
|
|
|
+ ?? products.GetValueOrDefault((Guid.Empty, item.Product.ID))?.FirstOrDefault()
|
|
|
+ ?? new CustomerProductSummary();
|
|
|
|
|
|
- foreach (var assignment in assignments)
|
|
|
+ double charge;
|
|
|
+ if (item.Charge.OverrideCharge)
|
|
|
{
|
|
|
-
|
|
|
- var id = timesummary switch
|
|
|
+ charge = quantity * item.Charge.Charge;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ charge = quantity * (product.Charge.PriceType switch
|
|
|
{
|
|
|
- InvoiceTimeCalculation.Detailed => assignment.ID,
|
|
|
- InvoiceTimeCalculation.Activity => assignment.ActivityLink.ID,
|
|
|
- _ => Guid.Empty
|
|
|
- };
|
|
|
+ ProductPriceType.CostPlus => 1 + product.Charge.Markup / 100,
|
|
|
+ _ => product.Charge.Price
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- var description = timesummary switch
|
|
|
+ if(!partlines.TryGetValue(id, out var partline))
|
|
|
+ {
|
|
|
+ partline = new InvoiceLineDetail
|
|
|
{
|
|
|
- InvoiceTimeCalculation.Detailed => string.Format("{0:dd MMM yy} - {1}", assignment.Date, assignment.Description),
|
|
|
- InvoiceTimeCalculation.Activity => assignment.ActivityLink.Description,
|
|
|
- _ => "Labour"
|
|
|
+ Description = description
|
|
|
};
|
|
|
-
|
|
|
- var quantity = assignment.Charge.OverrideQuantity
|
|
|
- ? TimeSpan.FromHours(assignment.Charge.Quantity)
|
|
|
- : assignment.Actual.Duration;
|
|
|
-
|
|
|
- var activity =
|
|
|
- activities.FirstOrDefault(x => x.Customer.ID.Equals(invoice.CustomerLink.ID) && x.Activity.ID.Equals(assignment.ActivityLink.ID))
|
|
|
- ?? activities.FirstOrDefault(x => x.Customer.ID.Equals(Guid.Empty) && x.Activity.ID.Equals(assignment.ActivityLink.ID))
|
|
|
- ?? new CustomerActivitySummary();
|
|
|
-
|
|
|
- double charge = 0.0F;
|
|
|
- if (assignment.Charge.OverrideCharge)
|
|
|
- charge = quantity.TotalHours * assignment.Charge.Charge;
|
|
|
- else
|
|
|
- {
|
|
|
-
|
|
|
- double fixedcharge = activity.Charge.FixedCharge;
|
|
|
-
|
|
|
- TimeSpan chargeperiod = !activity.Charge.ChargePeriod.Equals(TimeSpan.Zero)
|
|
|
- ? activity.Charge.ChargePeriod
|
|
|
- : TimeSpan.FromHours(1);
|
|
|
-
|
|
|
- var rounded = quantity.Ceiling(chargeperiod);
|
|
|
-
|
|
|
- double multiplier = TimeSpan.FromHours(1).TotalHours / chargeperiod.TotalHours;
|
|
|
- double rate = activity.Charge.ChargeRate * multiplier;
|
|
|
-
|
|
|
- double mincharge = activity.Charge.MinimumCharge;
|
|
|
-
|
|
|
- charge = Math.Max(fixedcharge + (rounded.TotalHours * rate), mincharge);
|
|
|
- }
|
|
|
-
|
|
|
- var timeline = timelines.FirstOrDefault(x => x.ID == id);
|
|
|
- if (timeline == null)
|
|
|
- {
|
|
|
- timeline = new InvoiceLineDetail();
|
|
|
- timeline.Description = description;
|
|
|
- timeline.TaxCode.ID = activity.Charge.TaxCode.ID;
|
|
|
- timeline.TaxCode.Synchronise(activity.Charge.TaxCode);
|
|
|
- timelines.Add(timeline);
|
|
|
- }
|
|
|
+ partline.TaxCode.CopyFrom(product.Product.TaxCode);
|
|
|
+ partlines.Add(id, partline);
|
|
|
+ }
|
|
|
|
|
|
- timeline.Quantity += quantity.TotalHours;
|
|
|
- timeline.Charge += charge;
|
|
|
+ partline.Quantity += quantity;
|
|
|
+ partline.Charge += charge;
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- foreach (var line in timelines)
|
|
|
+ return partlines.Values.ToArray(line =>
|
|
|
+ {
|
|
|
+ var update = new InvoiceLine();
|
|
|
+ update.InvoiceLink.ID = invoice.ID;
|
|
|
+ update.Description = line.Description;
|
|
|
+ update.TaxCode.CopyFrom(line.TaxCode);
|
|
|
+ update.Quantity = new[] { InvoiceMaterialCalculation.Detailed, InvoiceMaterialCalculation.Product }.Contains(partsummary) ? line.Quantity : 1.0F;
|
|
|
+ update.ExTax = line.Charge;
|
|
|
+ return update;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ private static async Task<InvoiceLine[]> ExpenseLines(Invoice invoice, InvoiceExpensesCalculation expensesSummary)
|
|
|
+ {
|
|
|
+ var billLinesTask = Task.Run(() =>
|
|
|
+ {
|
|
|
+ return Client.Query(
|
|
|
+ Filter<BillLine>.Where(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
|
|
|
+ Columns.None<BillLine>()
|
|
|
+ .Add(x => x.ID)
|
|
|
+ .Add(x => x.Description)
|
|
|
+ .Add(x => x.ExTax)
|
|
|
+ .Add(x => x.TaxCode.ID)
|
|
|
+ .Add(x => x.TaxCode.Rate)
|
|
|
+ .Add(x => x.Charge.OverrideCharge)
|
|
|
+ .Add(x => x.Charge.Charge)
|
|
|
+ .Add(x => x.Charge.OverrideQuantity)
|
|
|
+ .Add(x => x.Charge.Quantity))
|
|
|
+ .ToArray<BillLine>();
|
|
|
+ });
|
|
|
+
|
|
|
+ var expenselines = new Dictionary<Guid, InvoiceLineDetail>();
|
|
|
+
|
|
|
+ foreach (var item in await billLinesTask)
|
|
|
+ {
|
|
|
+ var id = expensesSummary switch
|
|
|
{
|
|
|
- var update = new InvoiceLine();
|
|
|
- update.InvoiceLink.ID = invoice.ID;
|
|
|
- update.Description = line.Description;
|
|
|
- update.TaxCode.ID = line.TaxCode.ID;
|
|
|
- update.TaxCode.Synchronise(line.TaxCode);
|
|
|
- update.Quantity = timesummary != InvoiceTimeCalculation.Collapsed ? line.Quantity : 1;
|
|
|
- update.ExTax = line.Charge;
|
|
|
- updates.Add(update);
|
|
|
- }
|
|
|
+ InvoiceExpensesCalculation.Detailed => item.ID,
|
|
|
+ _ => Guid.Empty
|
|
|
+ };
|
|
|
+
|
|
|
+ var description = expensesSummary switch
|
|
|
+ {
|
|
|
+ InvoiceExpensesCalculation.Detailed => $"{item.Description}",
|
|
|
+ _ => "Expenses"
|
|
|
+ };
|
|
|
|
|
|
- var partlines = new List<InvoiceLineDetail>();
|
|
|
+ var quantity = item.Charge.OverrideQuantity
|
|
|
+ ? item.Charge.Quantity
|
|
|
+ : 1.0;
|
|
|
|
|
|
- foreach (var item in movements)
|
|
|
+ double charge;
|
|
|
+ if (item.Charge.OverrideCharge)
|
|
|
{
|
|
|
+ charge = quantity * item.Charge.Charge;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ charge = quantity * item.ExTax * (1 + invoice.CustomerLink.Markup / 100);
|
|
|
+ }
|
|
|
|
|
|
- var id = partsummary switch
|
|
|
- {
|
|
|
- InvoiceMaterialCalculation.Detailed => item.ID,
|
|
|
- InvoiceMaterialCalculation.Product => item.Product.ID,
|
|
|
- InvoiceMaterialCalculation.CostCentre => item.Product.CostCentre.ID,
|
|
|
- _ => Guid.Empty
|
|
|
- };
|
|
|
-
|
|
|
- var description = partsummary switch
|
|
|
+ if(!expenselines.TryGetValue(id, out var expenseLine))
|
|
|
+ {
|
|
|
+ expenseLine = new InvoiceLineDetail
|
|
|
{
|
|
|
- InvoiceMaterialCalculation.Detailed => $"{item.Product.Name}: {item.Style.Code}; {item.Dimensions.UnitSize}",
|
|
|
- InvoiceMaterialCalculation.Product => item.Product.Name,
|
|
|
- InvoiceMaterialCalculation.CostCentre => item.Product.CostCentre.Description,
|
|
|
- _ => "Materials"
|
|
|
+ Description = description
|
|
|
};
|
|
|
-
|
|
|
- var quantity = item.Charge.OverrideQuantity
|
|
|
- ? item.Charge.Quantity
|
|
|
- : item.Qty;
|
|
|
-
|
|
|
- var product =
|
|
|
- products.FirstOrDefault(x => x.Customer.ID.Equals(invoice.CustomerLink.ID) && x.Product.ID.Equals(item.Product.ID))
|
|
|
- ?? products.FirstOrDefault(x => x.Customer.ID.Equals(Guid.Empty) && x.Product.ID.Equals(item.Product.ID))
|
|
|
- ?? new CustomerProductSummary();
|
|
|
-
|
|
|
- double charge = 0.0F;
|
|
|
- if (item.Charge.OverrideCharge)
|
|
|
- charge = quantity * item.Charge.Charge;
|
|
|
- else
|
|
|
- {
|
|
|
- charge = quantity * product.Charge.PriceType switch
|
|
|
- {
|
|
|
- ProductPriceType.CostPlus => 0.0F * product.Charge.Markup,
|
|
|
- _ => product.Charge.Price
|
|
|
- };
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- var partline = partlines.FirstOrDefault(x => x.ID == id);
|
|
|
- if (partline == null)
|
|
|
- {
|
|
|
- partline = new InvoiceLineDetail();
|
|
|
- partline.ID = id;
|
|
|
- partline.Description = description;
|
|
|
- partline.TaxCode.ID = product.Product.TaxCode.ID;
|
|
|
- partline.TaxCode.Synchronise(product.Product.TaxCode);
|
|
|
- partlines.Add(partline);
|
|
|
- }
|
|
|
-
|
|
|
- partline.Quantity += quantity;
|
|
|
- partline.Charge += charge;
|
|
|
-
|
|
|
+ expenseLine.TaxCode.CopyFrom(item.TaxCode);
|
|
|
+ expenselines.Add(id, expenseLine);
|
|
|
}
|
|
|
|
|
|
- foreach (var line in partlines)
|
|
|
- {
|
|
|
- var update = new InvoiceLine();
|
|
|
- update.InvoiceLink.ID = invoice.ID;
|
|
|
- update.Description = line.Description;
|
|
|
- update.TaxCode.ID = line.TaxCode.ID;
|
|
|
- update.TaxCode.Synchronise(line.TaxCode);
|
|
|
- update.Quantity = new[] { InvoiceMaterialCalculation.Detailed, InvoiceMaterialCalculation.Product}.Contains(partsummary) ? line.Quantity : 1.0F;
|
|
|
- update.ExTax = line.Charge;
|
|
|
- updates.Add(update);
|
|
|
- }
|
|
|
+ expenseLine.Quantity += quantity;
|
|
|
+ expenseLine.Charge += charge;
|
|
|
+ }
|
|
|
|
|
|
- progress?.Report("Creating Invoice Lines");
|
|
|
- if (updates.Any())
|
|
|
- new Client<InvoiceLine>().Save(updates, "Recalculating Invoice from Time and Materials");
|
|
|
-
|
|
|
+ return expenselines.Values.ToArray(line =>
|
|
|
+ {
|
|
|
+ var update = new InvoiceLine();
|
|
|
+ update.InvoiceLink.ID = invoice.ID;
|
|
|
+ update.Description = line.Description;
|
|
|
+ update.TaxCode.CopyFrom(line.TaxCode);
|
|
|
+ update.Quantity = expensesSummary != InvoiceExpensesCalculation.Collapsed ? line.Quantity : 1.0F;
|
|
|
+ update.ExTax = line.Charge;
|
|
|
+ return update;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void GenerateInvoiceLines(
|
|
|
+ Guid invoiceid,
|
|
|
+ InvoiceTimeCalculation timesummary,
|
|
|
+ InvoiceMaterialCalculation partsummary,
|
|
|
+ InvoiceExpensesCalculation expensesSummary,
|
|
|
+ IProgress<String>? progress
|
|
|
+ )
|
|
|
+ {
|
|
|
+ progress?.Report("Loading Invoice");
|
|
|
+ var invoice = Client.Query(
|
|
|
+ Filter<Invoice>.Where(x => x.ID).IsEqualTo(invoiceid))
|
|
|
+ .ToObjects<Invoice>().FirstOrDefault();
|
|
|
+ if(invoice is null)
|
|
|
+ {
|
|
|
+ Logger.Send(LogType.Error, "", $"Could not find invoice with ID {invoiceid}");
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ progress?.Report("Loading Detail Data");
|
|
|
+
|
|
|
+ var deleteOldTask = Task.Run(() =>
|
|
|
+ {
|
|
|
+ var oldlines = new Client<InvoiceLine>().Query(
|
|
|
+ Filter<InvoiceLine>.Where(x => x.InvoiceLink.ID).IsEqualTo(invoice.ID),
|
|
|
+ Columns.None<InvoiceLine>().Add(x => x.ID)
|
|
|
+ ).Rows.Select(x => x.ToObject<InvoiceLine>()).ToArray();
|
|
|
+ new Client<InvoiceLine>().Delete(oldlines, "");
|
|
|
+ });
|
|
|
+
|
|
|
+ var timeLinesTask = TimeLines(invoice, timesummary);
|
|
|
+ var partLinesTask = PartLines(invoice, partsummary);
|
|
|
+ var expenseLinesTask = ExpenseLines(invoice, expensesSummary);
|
|
|
|
|
|
+ progress?.Report("Calculating...");
|
|
|
+
|
|
|
+ var updates = CoreUtils.Concatenate(
|
|
|
+ timeLinesTask.Result,
|
|
|
+ partLinesTask.Result,
|
|
|
+ expenseLinesTask.Result);
|
|
|
+
|
|
|
+ progress?.Report("Creating Invoice Lines");
|
|
|
+ Client.Save(updates, "Recalculating Invoice from Time and Materials");
|
|
|
}
|
|
|
+
|
|
|
}
|