diff --git a/ImportAssist.slnx b/ImportAssist.slnx
index 559b508..f65393d 100644
--- a/ImportAssist.slnx
+++ b/ImportAssist.slnx
@@ -6,4 +6,7 @@
+
+
+
diff --git a/src/ImportAssist.Cli/Program.cs b/src/ImportAssist.Cli/Program.cs
index bd3d151..4672e24 100644
--- a/src/ImportAssist.Cli/Program.cs
+++ b/src/ImportAssist.Cli/Program.cs
@@ -17,39 +17,19 @@ if (!File.Exists(path))
return;
}
-var lines = File.ReadAllLines(path);
+IReadOnlyList rows;
-if (lines.Length == 0 ||
- lines[0] != "InvoiceId;CustomerId;Amount;Currency")
+try
{
- Console.Error.WriteLine(
- "Die Datei ist leer oder die Kopfzeile stimmt nicht.");
+ rows = InvoiceCsvParser.Parse(File.ReadAllText(path));
+}
+catch (FormatException exception)
+{
+ Console.Error.WriteLine(exception.Message);
Environment.ExitCode = 1;
return;
}
-var rows = new List();
-
-for (var index = 1; index < lines.Length; index++)
-{
- var line = lines[index];
- var columns = line.Split(';');
-
- if (line.Contains('"') || columns.Length != 4)
- {
- Console.Error.WriteLine(
- $"Zeile {index + 1}: Erwartet werden vier Spalten ohne Anführungszeichen.");
- Environment.ExitCode = 1;
- return;
- }
-
- rows.Add(new InvoiceRow(
- columns[0],
- columns[1],
- columns[2],
- columns[3]));
-}
-
foreach (var row in rows)
{
var errors = InvoiceValidator.Validate(row);
diff --git a/src/ImportAssist.Core/InvoiceCsvParser.cs b/src/ImportAssist.Core/InvoiceCsvParser.cs
new file mode 100644
index 0000000..cfecbbf
--- /dev/null
+++ b/src/ImportAssist.Core/InvoiceCsvParser.cs
@@ -0,0 +1,41 @@
+namespace ImportAssist.Core;
+
+public static class InvoiceCsvParser
+{
+ public static IReadOnlyList Parse(string csv)
+ {
+ using var reader = new StringReader(csv);
+
+ var header = reader.ReadLine()?.TrimStart('\uFEFF');
+
+ if (header != "InvoiceId;CustomerId;Amount;Currency")
+ {
+ throw new FormatException(
+ "Die Datei ist leer oder die Kopfzeile stimmt nicht.");
+ }
+
+ var rows = new List();
+ var lineNumber = 1;
+
+ while (reader.ReadLine() is { } line)
+ {
+ lineNumber++;
+
+ var columns = line.Split(';');
+
+ if (line.Contains('"') || columns.Length != 4)
+ {
+ throw new FormatException(
+ $"Zeile {lineNumber}: Erwartet werden vier Spalten ohne Anführungszeichen.");
+ }
+
+ rows.Add(new InvoiceRow(
+ columns[0],
+ columns[1],
+ columns[2],
+ columns[3]));
+ }
+
+ return rows;
+ }
+}
diff --git a/src/ImportAssist.Functions/ImportAssist_Functions.csproj b/src/ImportAssist.Functions/ImportAssist_Functions.csproj
index f3b163d..7a4f534 100644
--- a/src/ImportAssist.Functions/ImportAssist_Functions.csproj
+++ b/src/ImportAssist.Functions/ImportAssist_Functions.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/ImportAssist.Functions/InvoiceBlobTrigger.cs b/src/ImportAssist.Functions/InvoiceBlobTrigger.cs
new file mode 100644
index 0000000..24c622a
--- /dev/null
+++ b/src/ImportAssist.Functions/InvoiceBlobTrigger.cs
@@ -0,0 +1,66 @@
+using ImportAssist.Core;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Logging;
+
+namespace ImportAssist_Functions;
+
+public sealed class InvoiceBlobTrigger(
+ ILogger logger)
+{
+ [Function("InvoiceBlobTrigger")]
+ public void Run(
+ [BlobTrigger(
+ "imports/{name}",
+ Connection = "AzureWebJobsStorage")]
+ string content,
+ string name)
+ {
+ IReadOnlyList rows;
+
+ try
+ {
+ rows = InvoiceCsvParser.Parse(content);
+ }
+ 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));
+ }
+
+ logger.LogInformation(
+ "Dateiprüfung abgeschlossen: {BlobName}, " +
+ "{TotalCount} Rechnungen, {ValidCount} gültig, {InvalidCount} fehlerhaft.",
+ name,
+ rows.Count,
+ validCount,
+ rows.Count - validCount);
+ }
+}
\ No newline at end of file
diff --git a/tools/ImportAssist.Upload/ImportAssist.Upload.csproj b/tools/ImportAssist.Upload/ImportAssist.Upload.csproj
new file mode 100644
index 0000000..54b8ca8
--- /dev/null
+++ b/tools/ImportAssist.Upload/ImportAssist.Upload.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/tools/ImportAssist.Upload/Program.cs b/tools/ImportAssist.Upload/Program.cs
new file mode 100644
index 0000000..6675362
--- /dev/null
+++ b/tools/ImportAssist.Upload/Program.cs
@@ -0,0 +1,21 @@
+using Azure.Storage.Blobs;
+
+if (args.Length != 1 || !File.Exists(args[0]))
+{
+ Console.Error.WriteLine("Bitte eine vorhandene Datei angeben.");
+ Environment.ExitCode = 1;
+ return;
+}
+
+var container = new BlobContainerClient(
+ "UseDevelopmentStorage=true",
+ "imports");
+
+await container.CreateIfNotExistsAsync();
+
+var blobName = $"{Guid.NewGuid():N}-{Path.GetFileName(args[0])}";
+var blob = container.GetBlobClient(blobName);
+
+await blob.UploadAsync(args[0]);
+
+Console.WriteLine($"Hochgeladen: imports/{blobName}");
\ No newline at end of file