InvoiceUtilities.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. using Comal.Classes;
  2. using InABox.Clients;
  3. using InABox.Core;
  4. namespace PRS.Shared
  5. {
  6. public enum InvoiceTimeCalculation
  7. {
  8. Detailed,
  9. Activity,
  10. Collapsed,
  11. }
  12. public enum InvoiceMaterialCalculation
  13. {
  14. Detailed,
  15. Product,
  16. CostCentre,
  17. Collapsed,
  18. }
  19. public static class InvoiceUtilities
  20. {
  21. private class InvoiceLineDetail
  22. {
  23. public Guid ID { get; set; }
  24. public String Description { get; set; }
  25. public TaxCodeLink TaxCode { get; set; }
  26. public double Quantity { get; set; }
  27. public double Charge { get; set; }
  28. public InvoiceLineDetail()
  29. {
  30. TaxCode = new TaxCodeLink();
  31. }
  32. }
  33. public static void GenerateInvoiceLines(Guid invoiceid, InvoiceTimeCalculation timesummary, InvoiceMaterialCalculation partsummary, IProgress<String>? progress )
  34. {
  35. CustomerActivitySummary[] activities = new CustomerActivitySummary[] { };
  36. CustomerProductSummary[] products = new CustomerProductSummary[] { };
  37. Assignment[] assignments = new Assignment[] { };
  38. RequisitionItem[] requisitionitems = new RequisitionItem[] { };
  39. progress?.Report("Loading Invoice");
  40. var invoice = new Client<Invoice>().Load(new Filter<Invoice>(x => x.ID).IsEqualTo(invoiceid)).FirstOrDefault();
  41. progress?.Report("Loading Detail Data");
  42. var setup = new Task[]
  43. {
  44. Task.Run(() =>
  45. {
  46. var oldlines = new Client<InvoiceLine>().Query(
  47. new Filter<InvoiceLine>(x => x.InvoiceLink.ID).IsEqualTo(invoice.ID),
  48. new Columns<InvoiceLine>(x => x.ID)
  49. ).Rows.Select(x => x.ToObject<InvoiceLine>()).ToArray();
  50. new Client<InvoiceLine>().Delete(oldlines, "");
  51. }),
  52. Task.Run(() =>
  53. {
  54. activities =
  55. new Client<CustomerActivitySummary>().Query(
  56. new Filter<CustomerActivitySummary>(x => x.Customer.ID).InList(invoice.CustomerLink.ID, Guid.Empty),
  57. new Columns<CustomerActivitySummary>(x => x.Customer.ID)
  58. .Add(x => x.Activity.ID)
  59. .Add(x => x.Activity.Code)
  60. .Add(x => x.Activity.Description)
  61. .Add(x => x.Charge.TaxCode.ID)
  62. .Add(x => x.Charge.TaxCode.Rate)
  63. .Add(x => x.Charge.Chargeable)
  64. .Add(x => x.Charge.FixedCharge)
  65. .Add(x => x.Charge.ChargeRate)
  66. .Add(x => x.Charge.ChargePeriod)
  67. .Add(x => x.Charge.MinimumCharge)
  68. ).Rows.Select(r => r.ToObject<CustomerActivitySummary>()).ToArray();
  69. }),
  70. Task.Run(() =>
  71. {
  72. assignments = new Client<Assignment>().Query(
  73. new Filter<Assignment>(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
  74. null,
  75. new SortOrder<Assignment>(x => x.Date)
  76. ).Rows.Select(x => x.ToObject<Assignment>()).ToArray();
  77. }),
  78. Task.Run(() =>
  79. {
  80. products =
  81. new Client<CustomerProductSummary>().Query(
  82. new Filter<CustomerProductSummary>(x => x.Customer.ID).InList(invoice.CustomerLink.ID, Guid.Empty),
  83. new Columns<CustomerProductSummary>(x => x.Customer.ID)
  84. .Add(x => x.Product.ID)
  85. .Add(x => x.Product.Code)
  86. .Add(x => x.Product.Name)
  87. .Add(x => x.Product.TaxCode.ID)
  88. .Add(x => x.Product.TaxCode.Rate)
  89. .Add(x => x.Charge.Chargeable)
  90. .Add(x => x.Charge.PriceType)
  91. .Add(x => x.Charge.Price)
  92. .Add(x => x.Charge.Markup)
  93. ).Rows.Select(r => r.ToObject<CustomerProductSummary>()).ToArray();
  94. }),
  95. Task.Run(() =>
  96. {
  97. requisitionitems = new Client<RequisitionItem>().Query(
  98. new Filter<RequisitionItem>(x => x.Invoice.ID).IsEqualTo(invoice.ID).And(x => x.Charge.Chargeable).IsEqualTo(true),
  99. null,
  100. new SortOrder<RequisitionItem>(x => x.Picked)
  101. ).Rows.Select(x => x.ToObject<RequisitionItem>()).ToArray();
  102. })
  103. };
  104. Task.WaitAll(setup);
  105. List<InvoiceLine> updates = new List<InvoiceLine>();
  106. progress?.Report("Calculating...");
  107. var timelines = new List<InvoiceLineDetail>();
  108. foreach (var assignment in assignments)
  109. {
  110. var id = timesummary switch
  111. {
  112. InvoiceTimeCalculation.Detailed => assignment.ID,
  113. InvoiceTimeCalculation.Activity => assignment.ActivityLink.ID,
  114. _ => Guid.Empty
  115. };
  116. var description = timesummary switch
  117. {
  118. InvoiceTimeCalculation.Detailed => string.Format("{0:dd MMM yy} - {1}", assignment.Date, assignment.Description),
  119. InvoiceTimeCalculation.Activity => assignment.ActivityLink.Description,
  120. _ => "Labour"
  121. };
  122. var quantity = assignment.Charge.OverrideQuantity
  123. ? TimeSpan.FromHours(assignment.Charge.Quantity)
  124. : assignment.Actual.Duration;
  125. var activity =
  126. activities.FirstOrDefault(x => x.Customer.ID.Equals(invoice.CustomerLink.ID) && x.Activity.ID.Equals(assignment.ActivityLink.ID))
  127. ?? activities.FirstOrDefault(x => x.Customer.ID.Equals(Guid.Empty) && x.Activity.ID.Equals(assignment.ActivityLink.ID))
  128. ?? new CustomerActivitySummary();
  129. double charge = 0.0F;
  130. if (assignment.Charge.OverrideCharge)
  131. charge = quantity.TotalHours * assignment.Charge.Charge;
  132. else
  133. {
  134. double fixedcharge = activity.Charge.FixedCharge;
  135. TimeSpan chargeperiod = !activity.Charge.ChargePeriod.Equals(TimeSpan.Zero)
  136. ? activity.Charge.ChargePeriod
  137. : TimeSpan.FromHours(1);
  138. var rounded = quantity.Ceiling(chargeperiod);
  139. double multiplier = TimeSpan.FromHours(1).TotalHours / chargeperiod.TotalHours;
  140. double rate = activity.Charge.ChargeRate * multiplier;
  141. double mincharge = activity.Charge.MinimumCharge;
  142. charge = Math.Max(fixedcharge + (rounded.TotalHours * rate), mincharge);
  143. }
  144. var timeline = timelines.FirstOrDefault(x => x.ID == id);
  145. if (timeline == null)
  146. {
  147. timeline = new InvoiceLineDetail();
  148. timeline.Description = description;
  149. timeline.TaxCode.ID = activity.Charge.TaxCode.ID;
  150. timeline.TaxCode.Synchronise(activity.Charge.TaxCode);
  151. timelines.Add(timeline);
  152. }
  153. timeline.Quantity += quantity.TotalHours;
  154. timeline.Charge += charge;
  155. }
  156. foreach (var line in timelines)
  157. {
  158. var update = new InvoiceLine();
  159. update.InvoiceLink.ID = invoice.ID;
  160. update.Description = line.Description;
  161. update.TaxCode.ID = line.TaxCode.ID;
  162. update.TaxCode.Synchronise(line.TaxCode);
  163. update.Quantity = timesummary != InvoiceTimeCalculation.Collapsed ? line.Quantity : 1;
  164. update.ExTax = line.Charge;
  165. updates.Add(update);
  166. }
  167. var partlines = new List<InvoiceLineDetail>();
  168. foreach (var item in requisitionitems)
  169. {
  170. var id = partsummary switch
  171. {
  172. InvoiceMaterialCalculation.Detailed => item.ID,
  173. InvoiceMaterialCalculation.Product => item.Product.ID,
  174. InvoiceMaterialCalculation.CostCentre => item.Product.CostCentre.ID,
  175. _ => Guid.Empty
  176. };
  177. var description = partsummary switch
  178. {
  179. InvoiceMaterialCalculation.Detailed => item.Description,
  180. InvoiceMaterialCalculation.Product => item.Product.Name,
  181. InvoiceMaterialCalculation.CostCentre => item.Product.CostCentre.Description,
  182. _ => "Materials"
  183. };
  184. var quantity = item.Charge.OverrideQuantity
  185. ? item.Charge.Quantity
  186. : item.Quantity;
  187. var product =
  188. products.FirstOrDefault(x => x.Customer.ID.Equals(invoice.CustomerLink.ID) && x.Product.ID.Equals(item.Product.ID))
  189. ?? products.FirstOrDefault(x => x.Customer.ID.Equals(Guid.Empty) && x.Product.ID.Equals(item.Product.ID))
  190. ?? new CustomerProductSummary();
  191. double charge = 0.0F;
  192. if (item.Charge.OverrideCharge)
  193. charge = quantity * item.Charge.Charge;
  194. else
  195. {
  196. charge = quantity * product.Charge.PriceType switch
  197. {
  198. ProductPriceType.CostPlus => 0.0F * product.Charge.Markup,
  199. _ => product.Charge.Price
  200. };
  201. }
  202. var partline = partlines.FirstOrDefault(x => x.ID == id);
  203. if (partline == null)
  204. {
  205. partline = new InvoiceLineDetail();
  206. partline.Description = description;
  207. partline.TaxCode.ID = product.Product.TaxCode.ID;
  208. partline.TaxCode.Synchronise(product.Product.TaxCode);
  209. partlines.Add(partline);
  210. }
  211. partline.Quantity += quantity;
  212. partline.Charge += charge;
  213. }
  214. foreach (var line in partlines)
  215. {
  216. var update = new InvoiceLine();
  217. update.InvoiceLink.ID = invoice.ID;
  218. update.Description = line.Description;
  219. update.TaxCode.ID = line.TaxCode.ID;
  220. update.TaxCode.Synchronise(line.TaxCode);
  221. update.Quantity = new[] { InvoiceMaterialCalculation.Detailed, InvoiceMaterialCalculation.Product}.Contains(partsummary) ? line.Quantity : 1.0F;
  222. update.ExTax = line.Charge;
  223. updates.Add(update);
  224. }
  225. progress?.Report("Creating Invoice Lines");
  226. if (updates.Any())
  227. new Client<InvoiceLine>().Save(updates, "Recalculating Invoice from Time and Materials");
  228. }
  229. }
  230. }