diff --git a/.gitignore b/.gitignore
index 9d8d9f9..d7baaa0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-# Visual Studio
+# Visual Studio
.vs/
*.user
*.suo
@@ -38,3 +38,11 @@ secrets.cache
# Secret values file (never commit)
Scripts/secrets.json
+
+# Banking test data (sensitive real bank export files — never commit)
+Fuchs.Tests/TestData/Banking/*.zip
+Fuchs.Tests/TestData/Banking/*.xml
+Fuchs.Tests/TestData/Banking/*.camt
+Fuchs.Tests/TestData/Banking/*.sta
+Fuchs.Tests/TestData/Banking/*.mt940
+Fuchs.Tests/TestData/Banking/*.txt
diff --git a/CAMTParser/CamtParser.cs b/CAMTParser/CamtParser.cs
index 00727b7..0adf713 100644
--- a/CAMTParser/CamtParser.cs
+++ b/CAMTParser/CamtParser.cs
@@ -1,4 +1,5 @@
-using System.Globalization;
+using System.Globalization;
+using System.IO.Compression;
using System.Text;
using System.Xml.Linq;
@@ -32,6 +33,14 @@ public sealed class CamtParser
xml.Contains("camt.05", StringComparison.OrdinalIgnoreCase) ||
xml.Contains("BkToCstmr", StringComparison.OrdinalIgnoreCase);
+ ///
+ /// Cheap content sniff: is this payload a ZIP archive? Some banks deliver
+ /// camt.052 intraday reports wrapped in a ZIP containing one or more XML files.
+ /// Checks for the universal PK magic bytes (0x50 0x4B).
+ ///
+ public static bool LooksLikeZip(ReadOnlySpan bytes) =>
+ bytes.Length >= 2 && bytes[0] == 0x50 && bytes[1] == 0x4B;
+
public List Parse(Stream stream)
{
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, leaveOpen: true);
@@ -40,6 +49,39 @@ public sealed class CamtParser
public List Parse(byte[] bytes) => Parse(Encoding.UTF8.GetString(bytes));
+ ///
+ /// Parses all CAMT XML files found inside a ZIP archive and returns
+ /// the combined list of statements. Non-XML entries and malformed XML
+ /// entries are silently skipped. Used for camt.052 deliveries where the
+ /// bank wraps one or more intraday reports in a single ZIP file.
+ ///
+ public List ParseZip(byte[] bytes)
+ {
+ using var ms = new MemoryStream(bytes);
+ return ParseZip(ms);
+ }
+
+ ///
+ public List ParseZip(Stream stream)
+ {
+ var result = new List();
+ using var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: true);
+ foreach (var entry in archive.Entries)
+ {
+ if (!entry.Name.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
+ continue;
+ using var entryStream = entry.Open();
+ using var buffer = new MemoryStream();
+ entryStream.CopyTo(buffer);
+ var entryBytes = buffer.ToArray();
+ if (!LooksLikeXml(entryBytes))
+ continue;
+ try { result.AddRange(Parse(entryBytes)); }
+ catch (FormatException) { /* skip malformed XML entries */ }
+ }
+ return result;
+ }
+
public List Parse(string xml)
{
var result = new List();
diff --git a/Fuchs.Tests/CamtParserTests.cs b/Fuchs.Tests/CamtParserTests.cs
index 0bf3476..d893db4 100644
--- a/Fuchs.Tests/CamtParserTests.cs
+++ b/Fuchs.Tests/CamtParserTests.cs
@@ -1,6 +1,7 @@
-using System;
+using System;
using System.Data;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Text;
using CAMTParser;
@@ -156,6 +157,120 @@ public class CamtParserTests
=> Assert.Empty(new CamtParser().Parse("bar"));
}
+/// Tests for ZIP-wrapped camt.052 parsing.
+public class CamtZipParserTests
+{
+ // Minimal camt.052 with one credit entry.
+ private const string Camt052Xml = """
+
+
+
+ RPT12023-01-31T08:00:00
+
+ RPT-1
+ DE12345678901234567890EUR
+
+ 100.00
+ CRDT
+ 2023-01-15
+ 2023-01-15
+
+ Test Sender
+ Test Zahlung
+
+
+
+
+
+ """;
+
+ private static byte[] BuildZip(params (string name, string content)[] entries)
+ {
+ using var ms = new MemoryStream();
+ using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
+ {
+ foreach (var (name, content) in entries)
+ {
+ var entry = archive.CreateEntry(name);
+ using var s = entry.Open();
+ var b = Encoding.UTF8.GetBytes(content);
+ s.Write(b, 0, b.Length);
+ }
+ }
+ return ms.ToArray();
+ }
+
+ // ── LooksLikeZip ──────────────────────────────────────────────────────────
+ [Fact]
+ public void LooksLikeZip_ZipBytes_True()
+ {
+ var zip = BuildZip(("dummy.xml", Camt052Xml));
+ Assert.True(CamtParser.LooksLikeZip(zip));
+ }
+
+ [Fact]
+ public void LooksLikeZip_XmlBytes_False()
+ => Assert.False(CamtParser.LooksLikeZip(Encoding.UTF8.GetBytes(Camt052Xml)));
+
+ [Fact]
+ public void LooksLikeZip_Empty_False()
+ => Assert.False(CamtParser.LooksLikeZip(Array.Empty()));
+
+ // ── ParseZip ──────────────────────────────────────────────────────────────
+ [Fact]
+ public void ParseZip_SingleXmlEntry_ReturnsCamt052Statement()
+ {
+ var zip = BuildZip(("camt052.xml", Camt052Xml));
+ var statements = new CamtParser().ParseZip(zip);
+ var stmt = Assert.Single(statements);
+ Assert.Equal(CamtDocumentType.Camt052, stmt.DocumentType);
+ Assert.Equal("DE12345678901234567890", stmt.AccountIdentification);
+ Assert.Single(stmt.Entries);
+ Assert.Equal(100m, stmt.Entries[0].Amount);
+ Assert.Equal(CamtDebitCreditMark.Credit, stmt.Entries[0].Mark);
+ Assert.Equal("Test Sender", stmt.Entries[0].CounterpartyName);
+ }
+
+ [Fact]
+ public void ParseZip_MultipleXmlEntries_MergesAllStatements()
+ {
+ var zip = BuildZip(
+ ("report_a.xml", Camt052Xml),
+ ("report_b.xml", Camt052Xml));
+ var statements = new CamtParser().ParseZip(zip);
+ Assert.Equal(2, statements.Count);
+ Assert.All(statements, s => Assert.Equal(CamtDocumentType.Camt052, s.DocumentType));
+ }
+
+ [Fact]
+ public void ParseZip_NonXmlEntriesSkipped_NoException()
+ {
+ var zip = BuildZip(
+ ("readme.txt", "This is not XML"),
+ ("camt052.xml", Camt052Xml));
+ var statements = new CamtParser().ParseZip(zip);
+ Assert.Single(statements);
+ }
+
+ [Fact]
+ public void ParseZip_EmptyZip_ReturnsEmpty()
+ {
+ var zip = BuildZip();
+ Assert.Empty(new CamtParser().ParseZip(zip));
+ }
+
+ [Fact]
+ public void ParseZip_MalformedXmlEntry_SkippedGracefully()
+ {
+ var zip = BuildZip(
+ ("broken.xml", "BankingService routing: CAMT and MT940 both land in the same DataTable.
public class BankingDualFormatTests
{
@@ -220,4 +335,46 @@ public class BankingDualFormatTests
Assert.Equal(2, t.Columns.Count);
Assert.Equal(1, t.Rows.Count);
}
+
+ [Fact]
+ public void ParseToDatatable_CamtZip_RoutesToCamtZipAndMapsColumns()
+ {
+ const string camt052Xml = """
+
+
+
+ RPT12023-01-31T08:00:00
+
+ DE12345678901234567890EUR
+
+ 250.00CRDT
+ 2023-01-15
+
+ ZIP Sender
+ ZIP Zahlung
+
+
+
+
+
+ """;
+
+ using var ms = new MemoryStream();
+ using (var archive = new System.IO.Compression.ZipArchive(ms, System.IO.Compression.ZipArchiveMode.Create, leaveOpen: true))
+ {
+ var entry = archive.CreateEntry("camt052.xml");
+ using var es = entry.Open();
+ var b = Encoding.UTF8.GetBytes(camt052Xml);
+ es.Write(b, 0, b.Length);
+ }
+ ms.Position = 0;
+
+ var t = Svc.ParseToDatatable(ms);
+ Assert.Equal(1, t.Rows.Count);
+ Assert.Equal("DE12345678901234567890", t.Rows[0]["AccountIdentification"]);
+ Assert.Equal(250.00m, t.Rows[0]["Amount"]);
+ Assert.Equal("C", t.Rows[0]["DebitCreditMark"]);
+ Assert.Equal("ZIP Sender", t.Rows[0]["NameOfPayer"]);
+ Assert.Equal("ZIP Zahlung", t.Rows[0]["SepaRemittanceInformation"]);
+ }
}
diff --git a/Fuchs.Tests/CamtRealFileTests.cs b/Fuchs.Tests/CamtRealFileTests.cs
new file mode 100644
index 0000000..e0b5326
--- /dev/null
+++ b/Fuchs.Tests/CamtRealFileTests.cs
@@ -0,0 +1,162 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using CAMTParser;
+using Fuchs.Services;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Fuchs.Tests;
+
+///
+/// Smoke tests against real bank export files placed under TestData/Banking/.
+/// The files are git-ignored (sensitive financial data — see README.md there).
+///
+/// Placement:
+/// .zip → camt.052 archive from the bank (contains one or more XML reports)
+/// .xml → camt.053 (or camt.052/054) plain XML
+///
+/// When no files are present the tests pass trivially (loop body never runs).
+/// Once a file is placed and the project is rebuilt it is automatically tested.
+///
+/// Note: xUnit 2.x treats [Theory] with no data cases as a failure, so these
+/// tests use [Fact] + a loop over the file discovery results instead.
+///
+public class CamtRealFileTests
+{
+ private static readonly BankingService Svc = new(NullLogger.Instance);
+ private const string TestDataDir = "TestData/Banking";
+
+ // ── File discovery ────────────────────────────────────────────────────────
+
+ private static IEnumerable ZipFiles()
+ => FilesWithExtension(".zip");
+
+ private static IEnumerable XmlFiles()
+ => FilesWithExtension(".xml", ".camt");
+
+ private static IEnumerable AllBankingFiles()
+ => FilesWithExtension(".zip", ".xml", ".camt", ".sta", ".mt940");
+
+ private static IEnumerable FilesWithExtension(params string[] extensions)
+ {
+ if (!Directory.Exists(TestDataDir))
+ return Enumerable.Empty();
+ return Directory.EnumerateFiles(TestDataDir)
+ .Where(f => extensions.Any(ext =>
+ f.EndsWith(ext, System.StringComparison.OrdinalIgnoreCase)));
+ }
+
+ // ── ZIP files (camt.052 bank reports wrapped in a ZIP) ───────────────────
+
+ [Fact]
+ public void ParseZipFiles_EachReturnsAtLeastOneStatement()
+ {
+ foreach (var filePath in ZipFiles())
+ {
+ var bytes = File.ReadAllBytes(filePath);
+ Assert.True(CamtParser.LooksLikeZip(bytes),
+ $"{filePath}: expected ZIP magic bytes (PK).");
+
+ var statements = new CamtParser().ParseZip(bytes);
+ Assert.True(statements.Count > 0,
+ $"{filePath}: no CAMT statements found in ZIP.");
+
+ foreach (var stmt in statements)
+ AssertValidStatement(stmt, filePath);
+ }
+ }
+
+ [Fact]
+ public void ParseZipFiles_BankingService_EachReturnsRows()
+ {
+ foreach (var filePath in ZipFiles())
+ {
+ using var stream = File.OpenRead(filePath);
+ var table = Svc.ParseToDatatable(stream);
+ Assert.True(table.Rows.Count > 0,
+ $"{filePath}: BankingService.ParseToDatatable returned no rows.");
+ }
+ }
+
+ // ── XML files (camt.053 / camt.052 plain / camt.054) ─────────────────────
+
+ [Fact]
+ public void ParseXmlFiles_EachReturnsAtLeastOneStatement()
+ {
+ foreach (var filePath in XmlFiles())
+ {
+ var bytes = File.ReadAllBytes(filePath);
+ Assert.True(CamtParser.LooksLikeXml(bytes),
+ $"{filePath}: expected XML content.");
+ Assert.True(CamtParser.LooksLikeCamt(Encoding.UTF8.GetString(bytes)),
+ $"{filePath}: XML does not look like a CAMT document (missing 'camt.05' or 'BkToCstmr').");
+
+ var statements = new CamtParser().Parse(bytes);
+ Assert.True(statements.Count > 0,
+ $"{filePath}: no CAMT statements found.");
+
+ foreach (var stmt in statements)
+ AssertValidStatement(stmt, filePath);
+ }
+ }
+
+ [Fact]
+ public void ParseXmlFiles_BankingService_EachReturnsRows()
+ {
+ foreach (var filePath in XmlFiles())
+ {
+ using var stream = File.OpenRead(filePath);
+ var table = Svc.ParseToDatatable(stream);
+ Assert.True(table.Rows.Count > 0,
+ $"{filePath}: BankingService.ParseToDatatable returned no rows.");
+ }
+ }
+
+ // ── All files: DocumentType recognised ───────────────────────────────────
+
+ [Fact]
+ public void ParseAllBankingFiles_DocumentTypeIsKnown()
+ {
+ foreach (var filePath in AllBankingFiles())
+ {
+ var bytes = File.ReadAllBytes(filePath);
+ List statements;
+ if (CamtParser.LooksLikeZip(bytes))
+ statements = new CamtParser().ParseZip(bytes);
+ else if (CamtParser.LooksLikeXml(bytes))
+ statements = new CamtParser().Parse(bytes);
+ else
+ continue; // MT940 / other format — not a CAMT file
+
+ Assert.NotEmpty(statements);
+ Assert.All(statements, stmt =>
+ Assert.NotEqual(CamtDocumentType.Unknown, stmt.DocumentType));
+ }
+ }
+
+ // ── Shared assertions ─────────────────────────────────────────────────────
+
+ private static void AssertValidStatement(CamtStatement stmt, string filePath)
+ {
+ Assert.False(string.IsNullOrWhiteSpace(stmt.AccountIdentification),
+ $"{filePath}: AccountIdentification must not be blank.");
+ Assert.True(stmt.Entries.Count > 0,
+ $"{filePath}: statement for account '{stmt.AccountIdentification}' has no entries.");
+
+ Assert.All(stmt.Entries, e =>
+ {
+ Assert.True(e.Amount is > 0,
+ $"{filePath}: entry amount must be positive, got {e.Amount}.");
+ Assert.True(e.EntryDate.HasValue || e.ValueDate.HasValue,
+ $"{filePath}: entry must have at least one date (EntryDate or ValueDate).");
+ Assert.True(
+ e.Mark is CamtDebitCreditMark.Credit
+ or CamtDebitCreditMark.Debit
+ or CamtDebitCreditMark.ReverseCredit
+ or CamtDebitCreditMark.ReverseDebit,
+ $"{filePath}: entry has invalid DebitCreditMark '{e.Mark}'.");
+ });
+ }
+}
diff --git a/Fuchs.Tests/Fuchs.Tests.csproj b/Fuchs.Tests/Fuchs.Tests.csproj
index adb96fb..9121014 100644
--- a/Fuchs.Tests/Fuchs.Tests.csproj
+++ b/Fuchs.Tests/Fuchs.Tests.csproj
@@ -30,4 +30,13 @@
+
+
+
+ PreserveNewest
+
+
+
diff --git a/Fuchs.Tests/TestData/Banking/.gitkeep b/Fuchs.Tests/TestData/Banking/.gitkeep
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/Fuchs.Tests/TestData/Banking/.gitkeep
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Fuchs.Tests/TestData/Banking/README.md b/Fuchs.Tests/TestData/Banking/README.md
new file mode 100644
index 0000000..4802ad6
--- /dev/null
+++ b/Fuchs.Tests/TestData/Banking/README.md
@@ -0,0 +1,32 @@
+# Banking Test Samples
+
+Lege echte Bankexport-Dateien hier ab, um die Integrations-Smoketests in
+`CamtRealFileTests` zu aktivieren.
+
+> **Diese Dateien werden nie committed** (sensible Finanzdaten — gilt für alle
+> `.zip`, `.xml`, `.camt`, `.sta` und `.mt940` in diesem Verzeichnis).
+
+## Wo ablegen
+
+| Dateiformat | Erwarteter Inhalt | Beispieldateiname |
+|-------------|------------------|-------------------|
+| `.zip` | camt.052-ZIP-Archiv der Bank (enthält XML-Dateien) | `camt052_jan2025.zip` |
+| `.xml` | camt.053 (oder camt.052 / camt.054) als plain XML | `camt053_jan2025.xml` |
+| `.camt` | alternativer Dateiname für CAMT-XML | `umsaetze.camt` |
+
+Jede `.zip`- und `.xml`/`.camt`-Datei wird **automatisch** gefunden und getestet —
+keine Anpassung am Code nötig.
+
+## Was wird geprüft
+
+- ZIP-Dateien: `CamtParser.ParseZip` liefert ≥ 1 Statement mit IBAN und Einträgen
+- XML-Dateien: `CamtParser.Parse` erkennt das Dokument als CAMT und liefert valide Einträge
+- Beide: `BankingService.ParseToDatatable` liefert ≥ 1 Zeile in der DataTable
+- Dokumenttyp ist bekannt (nicht `Unknown`)
+- Jeder Eintrag hat Betrag > 0 und mindestens ein Datum
+
+## Hinweis
+
+Sind keine Dateien vorhanden, generiert xUnit 0 Testfälle (die Tests tauchen
+nicht im Runner auf). Sobald du eine Datei ablegst und den Build neu startest,
+erscheinen die Tests automatisch.
diff --git a/Fuchs/Program.cs b/Fuchs/Program.cs
index 1f109a5..d457563 100644
--- a/Fuchs/Program.cs
+++ b/Fuchs/Program.cs
@@ -36,6 +36,9 @@ public class Program
// Key Vault + DPAPI secret management (must run before FuchsOcmsIntranet.Initialize)
builder.AddSecretManagement();
+ // Assemble connection strings from templates + resolved credentials
+ AssembleConnectionStrings(builder.Configuration);
+
// Initialize the Fuchs intranet singleton with configuration
FuchsOcmsIntranet.Initialize(builder.Configuration);
@@ -163,4 +166,40 @@ public class Program
// One-time application start
Fuchs_intranet.SetPdfLicense();
}
+
+ ///
+ /// Replaces {username} and {password} tokens in connection string templates with the
+ /// resolved credential secrets, then overrides the config entries in-place.
+ /// When appsettings.Development.json supplies a complete connection string (no tokens),
+ /// the replace is a no-op and the original value is preserved.
+ ///
+ private static void AssembleConnectionStrings(ConfigurationManager config)
+ {
+ const string userToken = "{username}";
+ const string passToken = "{password}";
+
+ (string csName, string userKey, string passKey)[] pairs =
+ [
+ ("ocms_ConnectionString", "ConnectionStrings:ocms_username", "ConnectionStrings:ocms_password"),
+ ("fuchs_fds_ConnectionString", "ConnectionStrings:fuchs_fds_username", "ConnectionStrings:fuchs_fds_password"),
+ ];
+
+ var overrides = new Dictionary();
+ foreach (var (csName, userKey, passKey) in pairs)
+ {
+ var template = config.GetConnectionString(csName);
+ if (string.IsNullOrEmpty(template)) continue;
+ if (!template.Contains(userToken, StringComparison.Ordinal) &&
+ !template.Contains(passToken, StringComparison.Ordinal)) continue;
+
+ var user = config[userKey] ?? "";
+ var pass = config[passKey] ?? "";
+ overrides[$"ConnectionStrings:{csName}"] = template
+ .Replace(userToken, user, StringComparison.Ordinal)
+ .Replace(passToken, pass, StringComparison.Ordinal);
+ }
+
+ if (overrides.Count > 0)
+ config.AddInMemoryCollection(overrides);
+ }
}
diff --git a/Fuchs/Services/BankingService.cs b/Fuchs/Services/BankingService.cs
index 5e2e903..88da3ea 100644
--- a/Fuchs/Services/BankingService.cs
+++ b/Fuchs/Services/BankingService.cs
@@ -1,4 +1,4 @@
-using System.Data;
+using System.Data;
using System.Diagnostics;
using CAMTParser;
using Fuchs.Observability;
@@ -45,10 +45,17 @@ public class BankingService : IBankingService
}
string format;
- if (CamtParser.LooksLikeXml(bytes))
+ if (CamtParser.LooksLikeZip(bytes))
+ {
+ format = "camt.zip";
+ try { MapCamtEntries(tbl, new CamtParser().ParseZip(bytes)); }
+ catch (Exception ex) { _logger.LogError(ex, "CAMT ZIP statement parse failed."); }
+ }
+ else if (CamtParser.LooksLikeXml(bytes))
{
format = "camt";
- FillFromCamt(tbl, bytes);
+ try { MapCamtEntries(tbl, new CamtParser().Parse(bytes)); }
+ catch (Exception ex) { _logger.LogError(ex, "CAMT statement parse failed."); }
}
else
{
@@ -129,53 +136,48 @@ public class BankingService : IBankingService
}
// ── CAMT (ISO 20022) ───────────────────────────────────────────────────────
- private void FillFromCamt(DataTable tbl, byte[] bytes)
+ private void MapCamtEntries(DataTable tbl, List statements)
{
void SetNfo(DataRow nr, string key, object? value)
{
if (tbl.Columns.Contains(key) && value != null) nr[key] = value;
}
- try
+ foreach (var stmt in statements)
{
- var statements = new CamtParser().Parse(bytes);
- foreach (var stmt in statements)
+ if (string.IsNullOrEmpty(stmt.AccountIdentification)) continue;
+ foreach (var e in stmt.Entries)
{
- if (string.IsNullOrEmpty(stmt.AccountIdentification)) continue;
- foreach (var e in stmt.Entries)
+ try
{
- try
- {
- var nr = tbl.NewRow();
- SetNfo(nr, "AccountIdentification", stmt.AccountIdentification);
- if (e.Amount.HasValue) SetNfo(nr, "Amount", e.Amount);
- if (e.EntryDate.HasValue) SetNfo(nr, "EntryDate", e.EntryDate);
- if (e.ValueDate.HasValue) SetNfo(nr, "ValueDate", e.ValueDate);
- SetNfo(nr, "FundsCode", e.Currency);
- SetNfo(nr, "DebitCreditMark", e.MarkAbbreviation);
- SetNfo(nr, "BankReference", e.BankReference);
- SetNfo(nr, "EndToEndReference", e.EndToEndReference);
- SetNfo(nr, "MandateReference", e.MandateReference);
- SetNfo(nr, "CustomerReference", e.CustomerReference);
- SetNfo(nr, "CreditorReference", e.CreditorReference);
- SetNfo(nr, "AccountNumberOfPayer", e.CounterpartyIban);
- SetNfo(nr, "BankCodeOfPayer", e.CounterpartyBic);
- SetNfo(nr, "NameOfPayer", e.CounterpartyName);
- SetNfo(nr, "PostingText", e.AdditionalInfo);
- SetNfo(nr, "TransactionTypeIdCode",e.BankTransactionCode);
- SetNfo(nr, "SepaRemittanceInformation",
- string.IsNullOrEmpty(e.RemittanceUnstructured) ? e.RemittanceStructured : e.RemittanceUnstructured);
- SetNfo(nr, "UnstructuredRemittanceInformation", e.RemittanceUnstructured);
- SetNfo(nr, "UnstructuredData", e.RemittanceUnstructured);
- SetNfo(nr, "IsUnstructuredData", e.IsUnstructuredData);
+ var nr = tbl.NewRow();
+ SetNfo(nr, "AccountIdentification", stmt.AccountIdentification);
+ if (e.Amount.HasValue) SetNfo(nr, "Amount", e.Amount);
+ if (e.EntryDate.HasValue) SetNfo(nr, "EntryDate", e.EntryDate);
+ if (e.ValueDate.HasValue) SetNfo(nr, "ValueDate", e.ValueDate);
+ SetNfo(nr, "FundsCode", e.Currency);
+ SetNfo(nr, "DebitCreditMark", e.MarkAbbreviation);
+ SetNfo(nr, "BankReference", e.BankReference);
+ SetNfo(nr, "EndToEndReference", e.EndToEndReference);
+ SetNfo(nr, "MandateReference", e.MandateReference);
+ SetNfo(nr, "CustomerReference", e.CustomerReference);
+ SetNfo(nr, "CreditorReference", e.CreditorReference);
+ SetNfo(nr, "AccountNumberOfPayer", e.CounterpartyIban);
+ SetNfo(nr, "BankCodeOfPayer", e.CounterpartyBic);
+ SetNfo(nr, "NameOfPayer", e.CounterpartyName);
+ SetNfo(nr, "PostingText", e.AdditionalInfo);
+ SetNfo(nr, "TransactionTypeIdCode",e.BankTransactionCode);
+ SetNfo(nr, "SepaRemittanceInformation",
+ string.IsNullOrEmpty(e.RemittanceUnstructured) ? e.RemittanceStructured : e.RemittanceUnstructured);
+ SetNfo(nr, "UnstructuredRemittanceInformation", e.RemittanceUnstructured);
+ SetNfo(nr, "UnstructuredData", e.RemittanceUnstructured);
+ SetNfo(nr, "IsUnstructuredData", e.IsUnstructuredData);
- tbl.Rows.Add(nr);
- }
- catch (Exception ex) { _logger.LogWarning(ex, "CAMT entry parse error — account={Account}", stmt.AccountIdentification); }
+ tbl.Rows.Add(nr);
}
+ catch (Exception ex) { _logger.LogWarning(ex, "CAMT entry parse error — account={Account}", stmt.AccountIdentification); }
}
}
- catch (Exception ex) { _logger.LogError(ex, "CAMT statement parse failed."); }
}
private static DataTable BuildDefaultSchema()
diff --git a/Fuchs/appsettings.Development.json b/Fuchs/appsettings.Development.json
index a45cc86..76ca4d1 100644
--- a/Fuchs/appsettings.Development.json
+++ b/Fuchs/appsettings.Development.json
@@ -1,4 +1,8 @@
-{
+{
+ "ConnectionStrings": {
+ "ocms_ConnectionString": "Server=localhost;Database=ocms;User Id=DEV_USERNAME;Password=DEV_PASSWORD;TrustServerCertificate=True;",
+ "fuchs_fds_ConnectionString": "Server=localhost;Database=fuchs_fds;User Id=DEV_USERNAME;Password=DEV_PASSWORD;TrustServerCertificate=True;"
+ },
"Logging": {
"LogLevel": {
"Default": "Debug",
diff --git a/Fuchs/appsettings.json b/Fuchs/appsettings.json
index 479fbf9..d773025 100644
--- a/Fuchs/appsettings.json
+++ b/Fuchs/appsettings.json
@@ -5,8 +5,10 @@
"CacheFilePath": "secrets.cache",
"SyncIntervalHours": 6,
"ManagedSecretKeys": [
- "ConnectionStrings--ocms-ConnectionString",
- "ConnectionStrings--fuchs-fds-ConnectionString",
+ "ConnectionStrings--ocms-username",
+ "ConnectionStrings--ocms-password",
+ "ConnectionStrings--fuchs-fds-username",
+ "ConnectionStrings--fuchs-fds-password",
"Fuchs--SMS-APIKey",
"Fuchs--Mailer--Token",
"Fuchs--fuchs-captcha-TOTP",
@@ -21,8 +23,12 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
- "ocms_ConnectionString": "MANAGED_BY_KEYVAULT",
- "fuchs_fds_ConnectionString": "MANAGED_BY_KEYVAULT"
+ "ocms_ConnectionString": "Server=DB_SERVER;Database=ocms;User Id={username};Password={password};TrustServerCertificate=True;",
+ "fuchs_fds_ConnectionString": "Server=DB_SERVER;Database=fuchs_fds;User Id={username};Password={password};TrustServerCertificate=True;",
+ "ocms_username": "MANAGED_BY_KEYVAULT",
+ "ocms_password": "MANAGED_BY_KEYVAULT",
+ "fuchs_fds_username": "MANAGED_BY_KEYVAULT",
+ "fuchs_fds_password": "MANAGED_BY_KEYVAULT"
},
"Fuchs": {
"ocms_guid": "00094b8f-a822-4e9c-b627-87802f93fca8",