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 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user