diff --git a/src/ImportAssist.Core/ImportReport.cs b/src/ImportAssist.Core/ImportReport.cs new file mode 100644 index 0000000..a57fed2 --- /dev/null +++ b/src/ImportAssist.Core/ImportReport.cs @@ -0,0 +1,19 @@ +namespace ImportAssist.Core; + +public sealed record InvoiceCheckResult( + int LineNumber, + string InvoiceId, + bool IsValid, + IReadOnlyList Errors); + +public sealed record ImportReport( + string SourceBlob, + DateTimeOffset CheckedAtUtc, + string Status, + IReadOnlyList Rows, + IReadOnlyList FileErrors) +{ + public int TotalCount => Rows.Count; + public int ValidCount => Rows.Count(row => row.IsValid); + public int InvalidCount => TotalCount - ValidCount; +} diff --git a/src/ImportAssist.Functions/InvoiceBlobTrigger.cs b/src/ImportAssist.Functions/InvoiceBlobTrigger.cs index 24c622a..3ba61d0 100644 --- a/src/ImportAssist.Functions/InvoiceBlobTrigger.cs +++ b/src/ImportAssist.Functions/InvoiceBlobTrigger.cs @@ -1,3 +1,4 @@ +using System.Text.Json; using ImportAssist.Core; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; @@ -7,60 +8,69 @@ namespace ImportAssist_Functions; public sealed class InvoiceBlobTrigger( ILogger logger) { + private static readonly JsonSerializerOptions JsonOptions = + new(JsonSerializerDefaults.Web) + { + WriteIndented = true + }; + [Function("InvoiceBlobTrigger")] - public void Run( + [BlobOutput( + "results/{name}.json", + Connection = "AzureWebJobsStorage")] + public string Run( [BlobTrigger( "imports/{name}", Connection = "AzureWebJobsStorage")] string content, string name) { - IReadOnlyList rows; + ImportReport report; try { - rows = InvoiceCsvParser.Parse(content); + var rows = InvoiceCsvParser.Parse(content); + var results = new List(); + + for (var index = 0; index < rows.Count; index++) + { + var row = rows[index]; + var errors = InvoiceValidator.Validate(row); + + results.Add(new InvoiceCheckResult( + LineNumber: index + 2, + InvoiceId: row.InvoiceId, + IsValid: errors.Count == 0, + Errors: errors)); + } + + report = new ImportReport( + SourceBlob: name, + CheckedAtUtc: DateTimeOffset.UtcNow, + Status: "Completed", + Rows: results, + FileErrors: []); } catch (FormatException exception) { - logger.LogWarning( - "Datei {BlobName} wegen CSV-Formatfehler abgewiesen: {Reason}", - name, - exception.Message); - - return; - } - - var validCount = 0; - - foreach (var row in rows) - { - var errors = InvoiceValidator.Validate(row); - - if (errors.Count == 0) - { - validCount++; - } - - var invoiceId = string.IsNullOrWhiteSpace(row.InvoiceId) - ? "(ohne Rechnungsnummer)" - : row.InvoiceId; - - logger.LogInformation( - "{BlobName}: {InvoiceId}: {Result}", - name, - invoiceId, - errors.Count == 0 - ? "Gültig" - : string.Join("; ", errors)); + report = new ImportReport( + SourceBlob: name, + CheckedAtUtc: DateTimeOffset.UtcNow, + Status: "Rejected", + Rows: [], + FileErrors: [exception.Message]); } logger.LogInformation( - "Dateiprüfung abgeschlossen: {BlobName}, " + - "{TotalCount} Rechnungen, {ValidCount} gültig, {InvalidCount} fehlerhaft.", + "Prüfbericht erstellt: {BlobName}, Status {Status}, " + + "{TotalCount} Rechnungen, {ValidCount} gültig, " + + "{InvalidCount} fehlerhaft.", name, - rows.Count, - validCount, - rows.Count - validCount); + report.Status, + report.TotalCount, + report.ValidCount, + report.InvalidCount); + + return JsonSerializer.Serialize(report, JsonOptions); } } \ No newline at end of file diff --git a/tools/ImportAssist.Upload/Program.cs b/tools/ImportAssist.Upload/Program.cs index 6675362..503b3c5 100644 --- a/tools/ImportAssist.Upload/Program.cs +++ b/tools/ImportAssist.Upload/Program.cs @@ -1,5 +1,25 @@ using Azure.Storage.Blobs; +if (args.Length == 2 && args[0] == "result") +{ + var resultBlob = new BlobClient( + "UseDevelopmentStorage=true", + "results", + $"{args[1]}.json"); + + if (!(await resultBlob.ExistsAsync()).Value) + { + Console.Error.WriteLine( + "Bericht noch nicht vorhanden. Prüfe das Function-Log und den Blobnamen."); + Environment.ExitCode = 1; + return; + } + + var download = await resultBlob.DownloadContentAsync(); + Console.WriteLine(download.Value.Content.ToString()); + return; +} + if (args.Length != 1 || !File.Exists(args[0])) { Console.Error.WriteLine("Bitte eine vorhandene Datei angeben."); @@ -13,6 +33,12 @@ var container = new BlobContainerClient( await container.CreateIfNotExistsAsync(); +var resultsContainer = new BlobContainerClient( + "UseDevelopmentStorage=true", + "results"); + +await resultsContainer.CreateIfNotExistsAsync(); + var blobName = $"{Guid.NewGuid():N}-{Path.GetFileName(args[0])}"; var blob = container.GetBlobClient(blobName);