Persist invoice validation reports as JSON blobs

This commit is contained in:
admin committed 2026-09-07 16:26:02 +02:00
1 parent 2075bcb2c3
commit bb9b20cc25
3 files changed
+93 -38

No files matched your search

+19
View File
@@ -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 ImportAssist.Core;
using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -7,60 +8,69 @@ namespace ImportAssist_Functions;
public sealed class InvoiceBlobTrigger( public sealed class InvoiceBlobTrigger(
ILogger<InvoiceBlobTrigger> logger) ILogger<InvoiceBlobTrigger> logger)
{ {
private static readonly JsonSerializerOptions JsonOptions =
new(JsonSerializerDefaults.Web)
{
WriteIndented = true
};
[Function("InvoiceBlobTrigger")] [Function("InvoiceBlobTrigger")]
public void Run( [BlobOutput(
"results/{name}.json",
Connection = "AzureWebJobsStorage")]
public string Run(
[BlobTrigger( [BlobTrigger(
"imports/{name}", "imports/{name}",
Connection = "AzureWebJobsStorage")] Connection = "AzureWebJobsStorage")]
string content, string content,
string name) string name)
{ {
IReadOnlyList<InvoiceRow> rows; ImportReport report;
try 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) catch (FormatException exception)
{ {
logger.LogWarning( report = new ImportReport(
"Datei {BlobName} wegen CSV-Formatfehler abgewiesen: {Reason}", SourceBlob: name,
name, CheckedAtUtc: DateTimeOffset.UtcNow,
exception.Message); Status: "Rejected",
Rows: [],
return; FileErrors: [exception.Message]);
}
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));
} }
logger.LogInformation( logger.LogInformation(
"Dateiprüfung abgeschlossen: {BlobName}, " + "Prüfbericht erstellt: {BlobName}, Status {Status}, " +
"{TotalCount} Rechnungen, {ValidCount} gültig, {InvalidCount} fehlerhaft.", "{TotalCount} Rechnungen, {ValidCount} gültig, " +
"{InvalidCount} fehlerhaft.",
name, name,
rows.Count, report.Status,
validCount, report.TotalCount,
rows.Count - validCount); report.ValidCount,
report.InvalidCount);
return JsonSerializer.Serialize(report, JsonOptions);
} }
} }
+26
View File
@@ -1,5 +1,25 @@
using Azure.Storage.Blobs; 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])) if (args.Length != 1 || !File.Exists(args[0]))
{ {
Console.Error.WriteLine("Bitte eine vorhandene Datei angeben."); Console.Error.WriteLine("Bitte eine vorhandene Datei angeben.");
@@ -13,6 +33,12 @@ var container = new BlobContainerClient(
await container.CreateIfNotExistsAsync(); await container.CreateIfNotExistsAsync();
var resultsContainer = new BlobContainerClient(
"UseDevelopmentStorage=true",
"results");
await resultsContainer.CreateIfNotExistsAsync();
var blobName = $"{Guid.NewGuid():N}-{Path.GetFileName(args[0])}"; var blobName = $"{Guid.NewGuid():N}-{Path.GetFileName(args[0])}";
var blob = container.GetBlobClient(blobName); var blob = container.GetBlobClient(blobName);