| @@ -1,4 +1,4 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <AzureFunctionsVersion>v4</AzureFunctionsVersion> | |||
| @@ -7,12 +7,19 @@ | |||
| <Nullable>enable</Nullable> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Azure.Storage.Blobs" Version="12.12.0" /> | |||
| <PackageReference Include="Azure.Storage.Queues" Version="12.10.0" /> | |||
| <PackageReference Include="GemBox.Spreadsheet" Version="37.3.30.1110" /> | |||
| <PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" /> | |||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="4.0.4" /> | |||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.1.0" /> | |||
| <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.3.0" OutputItemType="Analyzer" /> | |||
| <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.6.0" /> | |||
| <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.SendGrid" Version="3.0.2" /> | |||
| <PackageReference Include="System.Core" Version="3.5.21022.801" /> | |||
| <PackageReference Include="System.Drawing.Common" Version="6.0.0" /> | |||
| <PackageReference Include="System.IO.Packaging" Version="6.0.0" /> | |||
| <PackageReference Include="System.Windows.Forms" Version="4.0.0" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <None Update="host.json"> | |||
| @@ -0,0 +1,219 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using GemBox.Spreadsheet; | |||
| using BlackRockReportFunction.Models; | |||
| using BlackRockReportFunction.Helpers; | |||
| using System.Windows.Forms; | |||
| namespace BlackRockReportFunction.Bussines | |||
| { | |||
| internal class ReportGenerator | |||
| { | |||
| public static async Task<ExcelFile> GenerateReportContent() | |||
| { | |||
| string licenseKey = "E0YU-JKLB-WFCE-N52P"; | |||
| SpreadsheetInfo.SetLicense(licenseKey); | |||
| var testObject = new ClockifyReport | |||
| { | |||
| reportName = "BlackRockReport_20220615", | |||
| reportDescription = "Total (13/06/2022 - 15/06/2022)", | |||
| reportPeople = new List<Person> | |||
| { | |||
| new Person | |||
| { | |||
| fullName = "Nikola Jovanovic", | |||
| records = new List<ClockifyRecord> | |||
| { | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 200 | |||
| }, | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 150 | |||
| } | |||
| } | |||
| }, | |||
| new Person | |||
| { | |||
| fullName = "Boris Stevanovic", | |||
| records = new List<ClockifyRecord> | |||
| { | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 300 | |||
| }, | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 100 | |||
| }, | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 120 | |||
| } | |||
| } | |||
| }, | |||
| new Person | |||
| { | |||
| fullName = "Dunja Stevanovic", | |||
| records = new List<ClockifyRecord> | |||
| { | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 50 | |||
| }, | |||
| new ClockifyRecord | |||
| { | |||
| recordDescription = "Description1", | |||
| recordTime = new TimeOnly(3,15,44), | |||
| amount = 500 | |||
| } | |||
| } | |||
| } | |||
| } | |||
| }; | |||
| var excelFile = new ExcelFile(); | |||
| var ws = excelFile.Worksheets.Add("Content"); | |||
| var sectionStyle = new CellStyle | |||
| { | |||
| Font = | |||
| { | |||
| Weight = ExcelFont.BoldWeight, | |||
| Size = 250, | |||
| }, | |||
| VerticalAlignment = VerticalAlignmentStyle.Center, | |||
| HorizontalAlignment = HorizontalAlignmentStyle.Center, | |||
| }; | |||
| var mainDetailsStyle = new CellStyle | |||
| { | |||
| Font = | |||
| { | |||
| Weight = ExcelFont.BoldWeight, | |||
| Size = 200, | |||
| } | |||
| }; | |||
| await AddReportItems(testObject, ws, sectionStyle, mainDetailsStyle); | |||
| // Autofit | |||
| excelFile = await AutoFitReport(excelFile); | |||
| return excelFile; | |||
| } | |||
| public static async Task AddReportItems(ClockifyReport reportObject, ExcelWorksheet ws, CellStyle sectionStyle, CellStyle mainDetailsStyle) | |||
| { | |||
| int row = 0; | |||
| string[] sectionNames = { "User", "Description", "Time (h)", "Time (decimal)", "Amount (USD)" }; | |||
| decimal totalAmountSum = 0; | |||
| for (int i = 0; i < sectionNames.Length; i++) | |||
| { | |||
| ws.Cells[row, i].Style = sectionStyle; | |||
| ws.Cells[row, i].Value = sectionNames[i]; | |||
| ws.Cells[row, i].Style.Borders[IndividualBorder.Right].LineStyle = LineStyle.Medium; | |||
| ws.Cells[row, i].Style.Borders[IndividualBorder.Bottom].LineStyle = LineStyle.Thick; | |||
| } | |||
| row++; | |||
| foreach (var reportPerson in reportObject.reportPeople) | |||
| { | |||
| ws.Cells[row, 0].Style = mainDetailsStyle; | |||
| ws.Cells[row, 0].Value = reportPerson.fullName; | |||
| var sumOfRecordHours = Formaters.getSumOfRecordTimes(reportPerson.records.Select(record => record.recordTime).ToList()); | |||
| ws.Cells[row, 2].Style = mainDetailsStyle; | |||
| ws.Cells[row, 2].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 2].Value = sumOfRecordHours; | |||
| ws.Cells[row, 3].Style = mainDetailsStyle; | |||
| ws.Cells[row, 3].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 3].Value = Formaters.getDecimalHours(sumOfRecordHours); | |||
| ws.Cells[row, 4].Style = mainDetailsStyle; | |||
| ws.Cells[row, 4].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 4].Value = string.Format("{0} USD", reportPerson.records.Sum(record => record.amount).ToString("0.00")); | |||
| totalAmountSum += reportPerson.records.Sum(record => record.amount); | |||
| row++; | |||
| foreach (var personRecord in reportPerson.records) | |||
| { | |||
| ws.Cells[row, 1].Value = personRecord.recordDescription; | |||
| ws.Cells[row, 2].Value = Formaters.getRecordTime(personRecord.recordTime); | |||
| ws.Cells[row, 2].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 3].Value = Formaters.getDecimalHours(personRecord.recordTime); | |||
| ws.Cells[row, 3].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 4].Value = string.Format("{0} USD", personRecord.amount.ToString("0.00")); | |||
| ws.Cells[row, 4].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| row++; | |||
| } | |||
| } | |||
| ws.Cells.GetSubrangeAbsolute(row, 0, row, 1).Merged = true; | |||
| ws.Cells.GetSubrangeAbsolute(row, 0, row, 1).Style = mainDetailsStyle; | |||
| ws.Cells.GetSubrangeAbsolute(row, 0, row, 1).Value = reportObject.reportDescription; | |||
| var totalSum = Formaters.getTotalSum( | |||
| reportObject.reportPeople.Select( | |||
| person => Formaters.getSumOfRecordTimes( | |||
| person.records.Select(record => record.recordTime).ToList() | |||
| ) | |||
| ).ToList() | |||
| ); | |||
| ws.Cells[row, 2].Style = mainDetailsStyle; | |||
| ws.Cells[row, 2].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 2].Value = totalSum; | |||
| ws.Cells[row, 3].Style = mainDetailsStyle; | |||
| ws.Cells[row, 3].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 3].Value = Formaters.getDecimalHours(totalSum); | |||
| ws.Cells[row, 4].Style = mainDetailsStyle; | |||
| ws.Cells[row, 4].Style.HorizontalAlignment = HorizontalAlignmentStyle.Right; | |||
| ws.Cells[row, 4].Value = string.Format("{0} USD", totalAmountSum.ToString("0.00")); | |||
| } | |||
| public static async Task<ExcelFile> AutoFitReport(ExcelFile excelFile) | |||
| { | |||
| var localFile = excelFile; | |||
| foreach (var sheet in localFile.Worksheets) | |||
| { | |||
| //var columnCount = sheet.CalculateMaxUsedColumns() - 1; | |||
| //for (int i = 0; i < columnCount; i++) | |||
| //{ | |||
| // sheet.Columns[i].AutoFit(1, sheet.Rows[0], sheet.Rows[sheet.Rows.Count - 1]); | |||
| //} | |||
| } | |||
| return localFile; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,102 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| using Microsoft.Extensions.Logging; | |||
| namespace BlackRockReportFunction.Helpers | |||
| { | |||
| public static class Formaters | |||
| { | |||
| public static string getSumOfRecordTimes(List<TimeOnly> recordTimes) | |||
| { | |||
| int totalHours = 0; | |||
| int totalMinutes = 0; | |||
| int totalSeconds = 0; | |||
| // Calculate Seconds | |||
| int totalRecordsSeconds = recordTimes.Sum(record => record.Second); | |||
| totalSeconds = totalRecordsSeconds % 60; | |||
| totalMinutes += totalRecordsSeconds / 60; | |||
| // Calculate Minutes | |||
| int totalRecordsMinutes = recordTimes.Sum(record => record.Minute); | |||
| totalMinutes = totalMinutes + (totalRecordsMinutes % 60); | |||
| totalHours += totalRecordsMinutes / 60; | |||
| // Calculate Hours | |||
| int totalRecordHours = recordTimes.Sum(record => record.Hour); | |||
| totalHours += totalRecordHours; | |||
| return string.Format("{0}:{1}:{2}", | |||
| totalHours > 9 ? totalHours : string.Format("0{0}", totalHours), | |||
| totalMinutes > 9 ? totalMinutes : string.Format("0{0}", totalMinutes), | |||
| totalSeconds > 9 ? totalSeconds : string.Format("0{0}", totalSeconds)); | |||
| } | |||
| public static string getDecimalHours(string recordHoursSum) | |||
| { | |||
| var components = recordHoursSum.Split(':').ToList(); | |||
| var hours = (components[0].Length == 2 && components[0].First() == '0') | |||
| ? components[0].ToCharArray()[1].ToString() | |||
| : components[0]; | |||
| var minutesPercent = (Convert.ToInt32(components[1]) * 100 / 60).ToString(); | |||
| if (minutesPercent.Length == 1) | |||
| { | |||
| minutesPercent = string.Format("0{0}", minutesPercent); | |||
| } | |||
| return string.Format("{0}:{1}", hours, minutesPercent); | |||
| } | |||
| public static string getDecimalHours(TimeOnly recordTime) | |||
| { | |||
| return getDecimalHours(string.Format("{0}:{1}:{2}", recordTime.Hour, recordTime.Minute, recordTime.Second)); | |||
| } | |||
| public static string getRecordTime(TimeOnly recordTime) | |||
| { | |||
| return string.Format("{0}:{1}:{2}", recordTime.Hour, recordTime.Minute, recordTime.Second); | |||
| } | |||
| public static string getTotalSum(List<string> personsSums) | |||
| { | |||
| var totalHours = 0; | |||
| var totalMinutes = 0; | |||
| var totalSeconds = 0; | |||
| foreach(var personSum in personsSums) | |||
| { | |||
| var components = personSum.Split(':').Select(Int32.Parse).ToList(); | |||
| //add new person time | |||
| totalSeconds += components[2] % 60; | |||
| totalMinutes += components[2] / 60; | |||
| //clean up new sum | |||
| totalMinutes += totalSeconds / 60; | |||
| totalSeconds = totalSeconds % 60; | |||
| //add new person time | |||
| totalMinutes += components[1] % 60; | |||
| totalHours += components[1] / 60; | |||
| //clean up new time | |||
| totalHours += totalMinutes / 60; | |||
| totalMinutes = totalMinutes % 60; | |||
| //add new person time | |||
| totalHours += components[0]; | |||
| } | |||
| return string.Format("{0}:{1}:{2}", | |||
| totalHours > 9 ? totalHours : string.Format("0{0}", totalHours), | |||
| totalMinutes > 9 ? totalMinutes : string.Format("0{0}", totalMinutes), | |||
| totalSeconds > 9 ? totalSeconds : string.Format("0{0}", totalSeconds)); | |||
| } | |||
| } | |||
| } | |||
| @@ -20,19 +20,20 @@ namespace BlackRockReportFunction | |||
| [Function("MailSenderFunction")] | |||
| public SendGridMessage Run([BlobTrigger("report-container/{name}", Connection = "AzureWebJobsStorage")] string fileData, string fileName) | |||
| { | |||
| _logger.LogInformation($"C# Blob trigger function Processed blob\n Name: {fileName} \n Data: {fileData}"); | |||
| _logger.LogInformation($"C# Blob trigger function Processed blob\n New file detected with name: {fileName}"); | |||
| var msg = new SendGridMessage() | |||
| { | |||
| From = new EmailAddress("[email protected]", "Nikola Jovanovic"), | |||
| Subject = "Test SendGrid Azure Function", | |||
| PlainTextContent = String.Format("If you read this text, then congratulations," + | |||
| " you did it! :)") | |||
| }; | |||
| return null; | |||
| //var msg = new SendGridMessage() | |||
| //{ | |||
| // From = new EmailAddress("[email protected]", "Nikola Jovanovic"), | |||
| // Subject = "Test SendGrid Azure Function", | |||
| // PlainTextContent = String.Format("If you read this text, then congratulations," + | |||
| // " you did it! :)") | |||
| //}; | |||
| msg.AddTo(new EmailAddress("[email protected]", "Nikola Jovanovic")); | |||
| //msg.AddTo(new EmailAddress("[email protected]", "Nikola Jovanovic")); | |||
| return msg; | |||
| //return msg; | |||
| } | |||
| } | |||
| } | |||
| @@ -6,10 +6,10 @@ using System.Threading.Tasks; | |||
| namespace BlackRockReportFunction.Models | |||
| { | |||
| internal class ClockifyRecord | |||
| public class ClockifyRecord | |||
| { | |||
| string recordDescription { get; set; } | |||
| TimeOnly recordTime { get; set; } | |||
| decimal amount { get; set; } | |||
| public string recordDescription { get; set; } | |||
| public TimeOnly recordTime { get; set; } | |||
| public decimal amount { get; set; } | |||
| } | |||
| } | |||
| @@ -6,10 +6,10 @@ using System.Threading.Tasks; | |||
| namespace BlackRockReportFunction.Models | |||
| { | |||
| internal class ClockifyReport | |||
| public class ClockifyReport | |||
| { | |||
| string ReportName { get; set; } | |||
| string ReportDescription { get; set; } | |||
| List<Person> reportPeople { get; set; } | |||
| public string reportName { get; set; } | |||
| public string reportDescription { get; set; } | |||
| public List<Person> reportPeople { get; set; } | |||
| } | |||
| } | |||
| @@ -6,9 +6,9 @@ using System.Threading.Tasks; | |||
| namespace BlackRockReportFunction.Models | |||
| { | |||
| internal class Person | |||
| public class Person | |||
| { | |||
| string FullName { get; set; } | |||
| List<ClockifyRecord> records { get; set; } | |||
| public string fullName { get; set; } | |||
| public List<ClockifyRecord> records { get; set; } | |||
| } | |||
| } | |||
| @@ -1,6 +1,13 @@ | |||
| using System; | |||
| using System.IO; | |||
| using Microsoft.Azure.Functions.Worker; | |||
| using Microsoft.Extensions.Logging; | |||
| using Microsoft.Azure.WebJobs; | |||
| using GemBox.Spreadsheet; | |||
| using BlackRockReportFunction.Models; | |||
| using BlackRockReportFunction.Helpers; | |||
| using Azure.Storage.Blobs; | |||
| using BlackRockReportFunction.Bussines; | |||
| namespace BlackRockReportFunction | |||
| { | |||
| @@ -14,9 +21,31 @@ namespace BlackRockReportFunction | |||
| } | |||
| [Function("ReportGeneratorFunction")] | |||
| public void Run([QueueTrigger("queue1")] string myQueueItem) | |||
| [BlobOutput("report-container/BlackRock_Report.xslx", Connection = "AzureWebJobsStorage")] | |||
| public async Task Run([QueueTrigger("queue1")] string myQueueItem) | |||
| { | |||
| _logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}"); | |||
| } | |||
| _logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}, gemboxKey: {"E0YU - JKLB - WFCE - N52P"}"); | |||
| var reportFile = await ReportGenerator.GenerateReportContent(); | |||
| string connection = Environment.GetEnvironmentVariable("AzureWebJobsStorage"); | |||
| string containerName = "report-container"; | |||
| _logger.LogInformation(connection); | |||
| var blobClient = new BlobContainerClient(connection, containerName); | |||
| var blob = blobClient.GetBlobClient($"BlackRockReport_{DateTime.Now.ToString("f")}.xlsx"); | |||
| Stream memoryStream = new MemoryStream(); | |||
| reportFile.Save(memoryStream, new XlsxSaveOptions | |||
| { | |||
| Type = XlsxType.Xlsx | |||
| }); | |||
| memoryStream.Position = 0; | |||
| await blob.UploadAsync(memoryStream); | |||
| _logger.LogInformation("File uploaded successfully!"); | |||
| } | |||
| } | |||
| } | |||