CustomerInvoices.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using Comal.Classes;
  8. using InABox.Clients;
  9. using InABox.Core;
  10. using InABox.DynamicGrid;
  11. using InABox.Reports;
  12. using InABox.Reports.Common;
  13. using InABox.WPF;
  14. namespace PRSDesktop
  15. {
  16. public class CustomerInvoices : DynamicDataGrid<Invoice>, IPanel<Invoice>
  17. {
  18. private readonly List<Tuple<Guid, string, string>> emails = new();
  19. private bool Outstanding = true;
  20. public CustomerInvoices()
  21. {
  22. Options.AddRange(DynamicGridOption.RecordCount, DynamicGridOption.FilterRows, DynamicGridOption.SelectColumns,
  23. DynamicGridOption.MultiSelect);
  24. HiddenColumns.Add(x => x.Number);
  25. HiddenColumns.Add(x => x.IncTax);
  26. HiddenColumns.Add(x => x.CustomerLink.ID);
  27. HiddenColumns.Add(x => x.CustomerLink.Name);
  28. HiddenColumns.Add(x => x.CustomerLink.Contact);
  29. HiddenColumns.Add(x => x.CustomerLink.Email);
  30. HiddenColumns.Add(x => x.JobLink.ID);
  31. HiddenColumns.Add(x => x.JobLink.JobNumber);
  32. HiddenColumns.Add(x => x.JobLink.Name);
  33. HiddenColumns.Add(x => x.Balance);
  34. AddButton("Email", PRSDesktop.Resources.email.AsBitmapImage(), DoEmailInvoice);
  35. AddButton("Show All", PRSDesktop.Resources.view.AsBitmapImage(), ToggleView);
  36. }
  37. public bool IsReady { get; set; }
  38. public Dictionary<string, object[]> Selected()
  39. {
  40. return new Dictionary<string, object[]> { { typeof(Invoice).EntityName(), SelectedRows } };
  41. }
  42. public event DataModelUpdateEvent OnUpdateDataModel;
  43. public void CreateToolbarButtons(IPanelHost host)
  44. {
  45. //
  46. }
  47. public string SectionName => "Customer Invoices";
  48. public DataModel DataModel(Selection selection)
  49. {
  50. var ids = ExtractValues(x => x.ID, selection).ToArray();
  51. return new InvoiceDataModel(new Filter<Invoice>(x => x.ID).InList(ids));
  52. }
  53. public bool Focus()
  54. {
  55. return true;
  56. }
  57. public void Refresh()
  58. {
  59. Refresh(false, true);
  60. }
  61. public void Setup()
  62. {
  63. Refresh(true, false);
  64. }
  65. public void Shutdown()
  66. {
  67. }
  68. public void Heartbeat(TimeSpan time)
  69. {
  70. }
  71. private bool ToggleView(Button sender, CoreRow[] rows)
  72. {
  73. Outstanding = !Outstanding;
  74. UpdateButton(sender, PRSDesktop.Resources.view.AsBitmapImage(), Outstanding ? "Show All" : "Outstanding");
  75. return true;
  76. }
  77. private List<string> CreateInvoiceFiles(CoreRow[] rows)
  78. {
  79. Progress.SetMessage("Loading Invoice Template");
  80. emails.Clear();
  81. var section = SectionName;
  82. var dataModel = DataModel(Selection.None);
  83. var client = new Client<ReportTemplate>();
  84. var template = client.Load(
  85. new Filter<ReportTemplate>(x => x.DataModel).IsEqualTo(dataModel.Name)
  86. .And(x => x.Section).IsEqualTo(section)
  87. .And(x => x.Name).IsEqualTo("Invoice"),
  88. new SortOrder<ReportTemplate>(x => x.Name)
  89. ).FirstOrDefault();
  90. if (template == null)
  91. template = new ReportTemplate { DataModel = dataModel.Name, Section = section, Name = "Invoice" };
  92. var files = new List<string>();
  93. foreach (var row in rows)
  94. {
  95. var InvoiceNumber = row.Get<Invoice, int>(x => x.Number);
  96. Progress.SetMessage(string.Format("Processing Invoice #{0}", InvoiceNumber));
  97. var dm = new InvoiceDataModel(new Filter<Invoice>(x => x.ID).IsEqualTo(row.Get<Invoice, Guid>(x => x.ID)));
  98. var pdf = ReportUtils.ReportToPDF(template, dm);
  99. var filename = Path.Combine(Path.GetTempPath(), string.Format("Invoice {0}.pdf", InvoiceNumber));
  100. File.WriteAllBytes(filename, pdf);
  101. files.Add(filename);
  102. }
  103. return files;
  104. }
  105. private string GenerateTemplate(CoreRow[] rows, Employee me)
  106. {
  107. var table = new HTMLStyle
  108. {
  109. { "border", "1px solid black" },
  110. { "border-collapse", "collapse" }
  111. };
  112. var cell = new HTMLStyle
  113. {
  114. { "border", "1px solid black" }
  115. };
  116. var html = new HTMLBuilder();
  117. var salutation = "Dear ";
  118. for (var i = 0; i < emails.Count; i++)
  119. {
  120. if (i > 0)
  121. salutation = salutation + (i == emails.Count - 1 ? " and " : ", ");
  122. salutation = salutation + emails[i].Item2.Split(' ').First();
  123. }
  124. salutation = salutation + ",";
  125. html = html.StartParagraph().StartFont("Arial", "initial").AddContent(salutation).EndFont().EndParagraph();
  126. html = html.StartParagraph().StartFont("Arial", "initial").AddContent("Please find attached the following Invoices:").EndFont()
  127. .EndParagraph();
  128. html = html.StartTable(table).AddAttribute("cellpadding", "2px")
  129. .StartRow()
  130. .StartCell(cell).AddAttribute("width", "100px").AddAttribute("align", "center").AddAttribute("bgcolor", "#AAAAAA")
  131. .StartFont("Arial", "initial").AddContent("Date").EndFont().EndCell()
  132. .StartCell(cell).AddAttribute("width", "90px").AddAttribute("align", "center").AddAttribute("bgcolor", "#AAAAAA")
  133. .StartFont("Arial", "initial").AddContent("Job #").EndFont().EndCell()
  134. .StartCell(cell).AddAttribute("width", "90px").AddAttribute("align", "center").AddAttribute("bgcolor", "#AAAAAA")
  135. .StartFont("Arial", "initial").AddContent("Invoice #").EndFont().EndCell()
  136. .StartCell(cell).AddAttribute("width", "400px").AddAttribute("align", "left").AddAttribute("bgcolor", "#AAAAAA")
  137. .StartFont("Arial", "initial").AddContent("Description").EndFont().EndCell()
  138. .StartCell(cell).AddAttribute("width", "100px").AddAttribute("align", "center").AddAttribute("bgcolor", "#AAAAAA")
  139. .StartFont("Arial", "initial").AddContent("Inc GST $").EndFont().EndCell()
  140. .EndRow();
  141. foreach (var row in rows)
  142. html = html
  143. .StartRow()
  144. .StartCell(cell).AddAttribute("align", "center").StartFont("Arial", "initial")
  145. .AddContentFormat("{0:dd MMM yy}", row.Get<Invoice, DateTime>(x => x.Date)).EndFont().EndCell()
  146. .StartCell(cell).AddAttribute("align", "center").StartFont("Arial", "initial")
  147. .AddContent(row.Get<Invoice, string>(x => x.JobLink.JobNumber)).EndFont().EndCell()
  148. .StartCell(cell).AddAttribute("align", "center").StartFont("Arial", "initial")
  149. .AddContentFormat("{0}", row.Get<Invoice, int>(x => x.Number)).EndFont().EndCell()
  150. .StartCell(cell).AddAttribute("align", "left").StartFont("Arial", "initial")
  151. .AddContent(row.Get<Invoice, string>(x => x.Description))
  152. .EndFont().EndCell()
  153. .StartCell(cell).AddAttribute("align", "center").StartFont("Arial", "initial")
  154. .AddContentFormat("${0:N2}", row.Get<Invoice, double>(x => x.IncTax)).EndFont().EndCell()
  155. .EndRow();
  156. html = html.EndTable();
  157. html = html.StartParagraph().StartFont("Arial", "initial")
  158. .AddContent("If you have any queries, please do not hesitate to contact us at any time.").EndFont().EndParagraph();
  159. html = html.StartParagraph().StartFont("Arial", "initial").AddContent("Regards,").EndFont().EndParagraph();
  160. html = html.StartParagraph().StartFont("Arial", "initial").AddContent(me.Name).EndFont().EndParagraph();
  161. return html.ToString();
  162. }
  163. private bool DoEmailInvoice(Button sender, CoreRow[] rows)
  164. {
  165. return EmailInvoices(rows);
  166. }
  167. public bool EmailInvoices(CoreRow[] rows)
  168. {
  169. if (!rows.Any())
  170. {
  171. MessageBox.Show("Please select at least one row to process");
  172. return false;
  173. }
  174. Progress.Show("Loading Data");
  175. var ids = rows.Select(r => r.Get<Invoice, Guid>(c => c.ID)).ToArray();
  176. var model = new InvoiceDataModel(new Filter<Invoice>(x => x.ID).InList(ids));
  177. var sectionName = SectionName;
  178. model.LoadModel(null);
  179. //model.Populate();
  180. var myemail = model.ExtractValues<Employee, string>(x => x.UserLink.EmailAddress).Distinct().FirstOrDefault();
  181. if (string.IsNullOrEmpty(myemail))
  182. {
  183. Progress.Close();
  184. MessageBox.Show("Logged in User does not have a valid email address!");
  185. return false;
  186. }
  187. Progress.SetMessage("Connecting to Mail");
  188. var mailer = ClientFactory.CreateMailer();
  189. if (!mailer.Connect())
  190. {
  191. Progress.Close();
  192. MessageBox.Show("Unable to connect to Mail System!");
  193. return false;
  194. }
  195. Progress.SetMessage("Parsing Emails");
  196. var emails = new List<Tuple<Guid, string, string>>();
  197. var custids = rows.Select(r =>
  198. new Tuple<Guid, string, string>(
  199. r.Get<Invoice, Guid>(c => c.CustomerLink.ID),
  200. r.Get<Invoice, string>(c => c.CustomerLink.Email),
  201. r.Get<Invoice, string>(c => c.CustomerLink.Contact)
  202. )
  203. ).Distinct().Where(x => x.Item1 != default).ToArray();
  204. if (custids.Any())
  205. {
  206. var contacts = new Client<CustomerContact>().Query(
  207. new Filter<CustomerContact>(x => x.Customer.ID).InList(custids.Select(x => x.Item1).ToArray())
  208. .And(x => x.Contact.Email).IsNotEqualTo("")
  209. .And(x => x.Type.AccountsPayable).IsEqualTo(true),
  210. new Columns<CustomerContact>(x => x.Customer.ID, x => x.Contact.Email, x => x.Contact.Name)
  211. );
  212. foreach (var contact in contacts.Rows)
  213. {
  214. var custid = contact.Get<CustomerContact, Guid>(x => x.ID);
  215. var email = contact.Get<CustomerContact, string>(x => x.Contact.Email);
  216. var name = contact.Get<CustomerContact, string>(x => x.Contact.Name);
  217. if (!emails.Any(x => string.Equals(x.Item2, email) && string.Equals(x.Item3, name)))
  218. emails.Add(new Tuple<Guid, string, string>(custid, email, name));
  219. }
  220. foreach (var cust in custids.Where(x => !string.IsNullOrWhiteSpace(x.Item2)))
  221. if (!emails.Any(x => x.Item1.Equals(cust.Item1)))
  222. emails.Add(new Tuple<Guid, string, string>(cust.Item1, cust.Item2, cust.Item3));
  223. }
  224. Progress.SetMessage("Loading Report Template");
  225. var client = new Client<ReportTemplate>();
  226. var report = client.Load(
  227. new Filter<ReportTemplate>(x => x.DataModel).IsEqualTo(model.Name)
  228. .And(x => x.Section).IsEqualTo(sectionName)
  229. .And(x => x.Name).IsEqualTo("Invoice"),
  230. new SortOrder<ReportTemplate>(x => x.Name)
  231. ).FirstOrDefault();
  232. if (report == null)
  233. report = new ReportTemplate { DataModel = model.Name, Section = sectionName, Name = "Invoice" };
  234. var numbers = model.ExtractValues<Invoice, int>(x => x.Number);
  235. Progress.SetMessage("Processing Invoices");
  236. var pdf = ReportUtils.ReportToPDF(report, model, false);
  237. var filename = Path.Combine(Path.GetTempPath(), string.Format("INV {0}.pdf", string.Join(" ", numbers)));
  238. File.WriteAllBytes(filename, pdf);
  239. Progress.SetMessage("Preparing Email");
  240. var template = @"
  241. <p><font face=""Arial"" size=""initial"">Dear
  242. {{ for c in Customer }}
  243. {{ len = array.size Customer }}
  244. {{ sep = """" }}
  245. {{
  246. if for.index == len-2
  247. sep = "" and ""
  248. else
  249. sep = "", ""
  250. end
  251. }}
  252. {{ c.Contact | string.split "" "" | array.first | string.strip }}{{ sep }}
  253. {{ end }}
  254. </font></p>
  255. <p><font face=""Arial"" size=""initial"">Please find attached the following Invoices:</font></p>
  256. <table style=""border:1px solid black;border-collapse:collapse;"" cellpadding=""2px"">
  257. <tr>
  258. <td style=""border:1px solid black;"" valign=""middle"" width=""100px"" align=""center"" bgcolor=""#AAAAAA""><font face=""Arial"" size=""initial"">Date</font></td>
  259. <td style=""border:1px solid black;"" valign=""middle"" width=""90px"" align=""center"" bgcolor=""#AAAAAA""><font face=""Arial"" size=""initial"">Job #</font></td>
  260. <td style=""border:1px solid black;"" valign=""middle"" width=""90px"" align=""center"" bgcolor=""#AAAAAA""><font face=""Arial"" size=""initial"">Invoice #</font></td>
  261. <td style=""border:1px solid black;"" valign=""middle"" width=""400px"" align=""left"" bgcolor=""#AAAAAA""><font face=""Arial"" size=""initial"">Description</font></td>
  262. <td style=""border:1px solid black;"" valign=""middle"" width=""100px"" align=""center"" bgcolor=""#AAAAAA""><font face=""Arial"" size=""initial"">Inc GST $</font></td>
  263. </tr>
  264. {{ for i in Invoice }}
  265. <tr>
  266. <td style=""border:1px solid black;"" valign=""middle"" align=""center""><font face=""Arial"" size=""initial"">{{ i.Date }}</font></td>
  267. <td style=""border:1px solid black;"" valign=""middle"" align=""center""><font face=""Arial"" size=""initial"">{{ i.JobLink.JobNumber }}</font></td>
  268. <td style=""border:1px solid black;"" valign=""middle"" align=""center""><font face=""Arial"" size=""initial"">{{ i.Number }}</font></td>
  269. <td style=""border:1px solid black;"" valign=""middle"" align=""left""><font face=""Arial"" size=""initial"">{{ i.Description }}</font></td>
  270. <td style=""border:1px solid black;"" valign=""middle"" align=""center""><font face=""Arial"" size=""initial"">${{ i.IncTax | math.format ""F2"" }}</font></td>
  271. </tr>
  272. {{ end }}
  273. </table>
  274. <p><font face=""Arial"" size=""initial"">If you have any queries, please do not hesitate to contact us at any time.</font></p>
  275. <p><font face=""Arial"" size=""initial"">Regards,</font></p>
  276. <p><font face=""Arial"" size=""initial"">
  277. {{ Employee | array.map ""Name"" | array.first }}<br>
  278. {{ Employee | array.map ""Email"" | array.first }}
  279. </font></p>
  280. ";
  281. var templates =
  282. new Client<DataModelTemplate>().Query(new Filter<DataModelTemplate>(x => x.Model).IsEqualTo(model.Name).And(x => x.Default)
  283. .IsEqualTo(true));
  284. if (!templates.Rows.Any())
  285. {
  286. var newtemplate = new DataModelTemplate
  287. {
  288. Model = model.Name,
  289. Name = "Invoice",
  290. Default = true,
  291. Template = template
  292. };
  293. new Client<DataModelTemplate>().Save(newtemplate, "Auto-Generated Template");
  294. }
  295. else
  296. {
  297. template = templates.Rows.First().Get<DataModelTemplate, string>(x => x.Template);
  298. }
  299. try
  300. {
  301. template = DataModelUtils.ParseTemplate(model, template);
  302. }
  303. catch (Exception e)
  304. {
  305. template = string.Format("Unable to Process Email Template!\n\n" + e.Message);
  306. }
  307. var message = mailer.CreateMessage();
  308. message.Subject = string.Format("New Invoice{0} Available: (#{1})", rows.Length > 1 ? "s" : "", string.Join(", #", numbers));
  309. message.From = myemail; // THIS SHOULD BE USER.SMTP?
  310. message.To = emails.Select(x => x.Item2).ToArray();
  311. message.IsHTML = true;
  312. message.Body = template;
  313. var attach = new List<Tuple<string, byte[]>>();
  314. attach.Add(new Tuple<string, byte[]>(Path.GetFileName(filename), File.ReadAllBytes(filename)));
  315. message.Attachments = attach.ToArray();
  316. var form = new EmailForm(message, model) { Zoom = 100F };
  317. Progress.Close();
  318. if (form.ShowDialog() == true)
  319. mailer.SendMessage(message);
  320. return false;
  321. }
  322. protected override void Reload(Filters<Invoice> criteria, Columns<Invoice> columns, ref SortOrder<Invoice> sort,
  323. Action<CoreTable, Exception> action)
  324. {
  325. if (Outstanding)
  326. criteria.Add(new Filter<Invoice>(x => x.Balance).IsNotEqualTo(0.0F));
  327. base.Reload(criteria, columns, ref sort, action);
  328. }
  329. protected override bool FilterRecord(CoreRow row)
  330. {
  331. if (Outstanding)
  332. return Math.Abs(row.Get<Invoice, double>(x => x.Balance)) > 0.01F;
  333. return base.FilterRecord(row);
  334. }
  335. }
  336. }