Persist invoice validation reports as JSON blobs
This commit is contained in:
1 parent
2075bcb2c3
commit
bb9b20cc25
3 files changed
+93
-38
No files matched your search
@@ -0,0 +1,19 @@
|
||||
namespace ImportAssist.Core;
|
||||
|
||||
public sealed record InvoiceCheckResult(
|
||||
int LineNumber,
|
||||
string InvoiceId,
|
||||
bool IsValid,
|
||||
IReadOnlyList<string> Errors);
|
||||
|
||||
public sealed record ImportReport(
|
||||
string SourceBlob,
|
||||
DateTimeOffset CheckedAtUtc,
|
||||
string Status,
|
||||
IReadOnlyList<InvoiceCheckResult> Rows,
|
||||
IReadOnlyList<string> FileErrors)
|
||||
{
|
||||
public int TotalCount => Rows.Count;
|
||||
public int ValidCount => Rows.Count(row => row.IsValid);
|
||||
public int InvalidCount => TotalCount - ValidCount;
|
||||
}
|
||||
@@ -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<InvoiceBlobTrigger> 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<InvoiceRow> rows;
|
||||
ImportReport report;
|
||||
|
||||
try
|
||||
{
|
||||
rows = InvoiceCsvParser.Parse(content);
|
||||
var rows = InvoiceCsvParser.Parse(content);
|
||||
var results = new List<InvoiceCheckResult>();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in new issue
Block a user