private void ProcessDocument(Document document) { statements = new List <TStatement>(); AccountStatement2[] stmts = document.BkToCstmrStmt.Stmt; foreach (AccountStatement2 accStatement in stmts) { TStatement stmt = new TStatement(); stmt.id = accStatement.Id; stmt.elctrncSeqNb = accStatement.ElctrncSeqNb.ToString(); stmt.accountCode = accStatement.Acct?.Id?.Item?.ToString(); stmt.bankCode = accStatement.Acct?.Svcr?.FinInstnId?.BIC; stmt.currency = accStatement.Acct?.Ccy; stmt.severalYears = false; string nm = accStatement.Acct?.Ownr?.Nm; string ownName = nm ?? "AccountNameFor" + stmt.bankCode + "/" + stmt.accountCode; CashBalance3[] balances = accStatement.Bal; if (balances != null) { foreach (CashBalance3 balance in balances) { // PRCD: PreviouslyClosedBooked if (balance.Tp?.CdOrPrtry?.Item?.ToString() == "PRCD") { stmt.startBalance = balance.Amt.Value; // CreditDebitIndicator: CRDT or DBIT for credit or debit if (balance.CdtDbtInd == CreditDebitCode.DBIT) { stmt.startBalance *= -1.0m; } stmt.startDate = balance.Dt.Item; } // CLBD: ClosingBooked else if (balance.Tp?.CdOrPrtry?.Item?.ToString() == "CLBD") { stmt.endBalance = balance.Amt.Value; // CreditDebitIndicator: CRDT or DBIT for credit or debit if (balance.CdtDbtInd == CreditDebitCode.DBIT) { stmt.endBalance *= -1.0m; } stmt.endDate = balance.Dt.Item; } // ITBD: InterimBooked // CLAV: ClosingAvailable // FWAV: ForwardAvailable } } //string strDiffBalance = "DiffBalanceFor" + stmt.bankCode + "/" + stmt.accountCode; //Decimal DiffBalance = 0.0m; //if (Decimal.TryParse(strDiffBalance, out DiffBalance)) //{ // stmt.startBalance += DiffBalance; // stmt.endBalance += DiffBalance; //} //else //{ // Log.Write("problem parsing decimal from configuration setting DiffBalanceFor" + stmt.bankCode + "/" + stmt.accountCode); //} //string filenameWithoutExtension = Path.GetFileNameWithoutExtension(filename); //// if the file has already been split and moved, use the statement date from the file name (if it is on the last of december) //if (!filenameWithoutExtension.Contains("_C53_") // && filenameWithoutExtension.EndsWith("1231") // && stmt.date.Month != 12 // && stmt.date.Day != 31) //{ // stmt.date = new DateTime(stmt.date.Year - 1, 12, 31); //} ReportEntry2[] entries = accStatement.Ntry; if (entries != null) { foreach (ReportEntry2 entry in entries) { if (entry.Amt.Ccy != stmt.currency) { throw new Exception("transaction currency " + entry.Amt.Ccy + " does not match the bank statement currency"); } TTransaction tr = new TTransaction(); tr.pending = entry.Sts == EntryStatus2Code.PDNG; if (entry.BookgDt != null) { tr.inputDate = entry.BookgDt.Item; } if (entry.ValDt != null) { tr.valueDate = entry.ValDt.Item; if (tr.valueDate.Year != stmt.startDate.Year) { stmt.severalYears = true; } } // Soll/Haben tr.amount = entry.Amt.Value; bool debit = entry.CdtDbtInd == CreditDebitCode.DBIT; if (debit) { tr.amount *= -1.0m; } tr.storno = entry.RvslInd; EntryDetails1 entryDetails = entry.NtryDtls?.FirstOrDefault(); EntryTransaction2 txDetails = entryDetails?.TxDtls?.FirstOrDefault(); // Verwendungszweck if (txDetails?.RmtInf?.Ustrd != null) { tr.description = string.Join(string.Empty, txDetails.RmtInf.Ustrd.Select(s => s?.Trim())); } tr.text = entry.AddtlNtryInf?.Trim(); tr.bankCode = debit ? txDetails?.RltdAgts?.CdtrAgt?.FinInstnId?.BIC : txDetails?.RltdAgts?.DbtrAgt?.FinInstnId?.BIC; tr.partnerName = debit ? txDetails?.RltdPties?.Cdtr?.Nm : txDetails?.RltdPties?.Dbtr?.Nm; tr.accountCode = debit ? txDetails?.RltdPties?.CdtrAcct?.Id?.Item?.ToString() : txDetails?.RltdPties?.DbtrAcct?.Id?.Item?.ToString(); string CrdtName = txDetails?.RltdPties?.Cdtr?.Nm; string DbtrName = txDetails?.RltdPties?.Dbtr?.Nm; if ((CrdtName != null) && (CrdtName != ownName)) { if ((DbtrName != null) && (DbtrName == ownName)) { // we are the debitor } else if (ownName != string.Empty) { // sometimes donors write the project or recipient in the field where the organisation is supposed to be Log.Write("CrdtName is not like expected: " + tr.description + " --- " + CrdtName); } } tr.endToEndId = txDetails?.Refs?.EndToEndId; tr.msgId = txDetails?.Refs?.MsgId; tr.pmtInfId = txDetails?.Refs?.PmtInfId; tr.mndtId = txDetails?.Refs?.MndtId; tr.id = txDetails?.Refs?.Prtry?.Ref; tr.customerRef = entry.AcctSvcrRef; if (txDetails?.BkTxCd.Prtry.Cd != null) { // eg NSTO+152+00900. look for SEPA Geschäftsvorfallcodes // see the codes: https://www.hettwer-beratung.de/business-portfolio/zahlungsverkehr/elektr-kontoinformationen-swift-mt-940/ string[] GVCCode = txDetails?.BkTxCd?.Prtry?.Cd?.Split(new char[] { '+' }); if (GVCCode.Length > 0) { tr.transactionTypeId = GVCCode[0]; } if (GVCCode.Length > 1) { tr.typecode = GVCCode[1]; } } // for SEPA direct debit batches, there are multiple TxDtls records if (entryDetails?.TxDtls?.Count() > 1) { tr.partnerName = string.Empty; tr.description = string.Format("SEPA Sammel-Basislastschrift mit {0} Lastschriften", entryDetails?.TxDtls?.Count()); } stmt.transactions.Add(tr); Log.Write("count : " + stmt.transactions.Count.ToString()); } } statements.Add(stmt); } }
/// <summary> /// processing CAMT file /// </summary> /// <param name="filename"></param> public void ProcessFile(string filename) { Console.WriteLine("Read file " + filename); statements = new List <TStatement>(); CultureInfo backupCulture = Thread.CurrentThread.CurrentCulture; try { XmlDocument doc = new XmlDocument(); doc.Load(filename); string namespaceName = "urn:iso:std:iso:20022:tech:xsd:camt.052.001.02"; XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("camt", namespaceName); XmlNode nodeDocument = doc.DocumentElement; if ((nodeDocument == null) || (nodeDocument.Attributes["xmlns"].Value != namespaceName)) { throw new Exception("expecting xmlns = '" + namespaceName + "'"); } XmlNodeList stmts = nodeDocument.SelectNodes("camt:BkToCstmrAcctRpt/camt:Rpt", nsmgr); Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; foreach (XmlNode nodeStatement in stmts) { TStatement stmt = new TStatement(); currentStatement = stmt; stmt.id = nodeStatement.SelectSingleNode("camt:ElctrncSeqNb", nsmgr).InnerText; stmt.accountCode = nodeStatement.SelectSingleNode("camt:Acct/camt:Id/camt:IBAN", nsmgr).InnerText; stmt.bankCode = nodeStatement.SelectSingleNode("camt:Acct/camt:Svcr/camt:FinInstnId/camt:BIC", nsmgr).InnerText; stmt.currency = nodeStatement.SelectSingleNode("camt:Acct/camt:Ccy", nsmgr).InnerText; stmt.severalYears = false; XmlNode nm = nodeStatement.SelectSingleNode("camt:Acct/camt:Ownr/camt:Nm", nsmgr); string ownName = nm != null ? nm.InnerText : "AccountNameFor" + stmt.bankCode + "/" + stmt.accountCode; XmlNodeList nodeBalances = nodeStatement.SelectNodes("camt:Bal", nsmgr); foreach (XmlNode nodeBalance in nodeBalances) { // PRCD: PreviouslyClosedBooked if (nodeBalance.SelectSingleNode("camt:Tp/camt:CdOrPrtry/camt:Cd", nsmgr).InnerText == "PRCD") { stmt.startBalance = Decimal.Parse(nodeBalance.SelectSingleNode("camt:Amt", nsmgr).InnerText); // CreditDebitIndicator: CRDT or DBIT for credit or debit if (nodeBalance.SelectSingleNode("camt:CdtDbtInd", nsmgr).InnerText == "DBIT") { stmt.startBalance *= -1.0m; } stmt.date = DateTime.Parse(nodeBalance.SelectSingleNode("camt:Dt", nsmgr).InnerText); } // CLBD: ClosingBooked else if (nodeBalance.SelectSingleNode("camt:Tp/camt:CdOrPrtry/camt:Cd", nsmgr).InnerText == "CLBD") { stmt.endBalance = Decimal.Parse(nodeBalance.SelectSingleNode("camt:Amt", nsmgr).InnerText); // CreditDebitIndicator: CRDT or DBIT for credit or debit if (nodeBalance.SelectSingleNode("camt:CdtDbtInd", nsmgr).InnerText == "DBIT") { stmt.endBalance *= -1.0m; } stmt.date = DateTime.Parse(nodeBalance.SelectSingleNode("camt:Dt", nsmgr).InnerText); } // ITBD: InterimBooked // CLAV: ClosingAvailable // FWAV: ForwardAvailable } string strDiffBalance = "DiffBalanceFor" + stmt.bankCode + "/" + stmt.accountCode; Decimal DiffBalance = 0.0m; if (Decimal.TryParse(strDiffBalance, out DiffBalance)) { stmt.startBalance += DiffBalance; stmt.endBalance += DiffBalance; } else { Log.Write("problem parsing decimal from configuration setting DiffBalanceFor" + stmt.bankCode + "/" + stmt.accountCode); } string filenameWithoutExtension = Path.GetFileNameWithoutExtension(filename); // if the file has already been split and moved, use the statement date from the file name (if it is on the last of december) if (!filenameWithoutExtension.Contains("_C53_") && filenameWithoutExtension.EndsWith("1231") && stmt.date.Month != 12 && stmt.date.Day != 31) { stmt.date = new DateTime(stmt.date.Year - 1, 12, 31); } XmlNodeList nodeEntries = nodeStatement.SelectNodes("camt:Ntry", nsmgr); foreach (XmlNode nodeEntry in nodeEntries) { TTransaction tr = new TTransaction(); tr.inputDate = DateTime.Parse(nodeEntry.SelectSingleNode("camt:BookgDt/camt:Dt", nsmgr).InnerText); tr.valueDate = DateTime.Parse(nodeEntry.SelectSingleNode("camt:ValDt/camt:Dt", nsmgr).InnerText); if (tr.valueDate.Year != stmt.date.Year) { // ignore transactions that are in a different year than the statement stmt.severalYears = true; continue; } tr.amount = Decimal.Parse(nodeEntry.SelectSingleNode("camt:Amt", nsmgr).InnerText); if (nodeEntry.SelectSingleNode("camt:Amt", nsmgr).Attributes["Ccy"].Value != stmt.currency) { throw new Exception("transaction currency " + nodeEntry.SelectSingleNode("camt:Amt", nsmgr).Attributes["Ccy"].Value + " does not match the bank statement currency"); } if (nodeEntry.SelectSingleNode("camt:CdtDbtInd", nsmgr).InnerText == "DBIT") { tr.amount *= -1.0m; } XmlNode desc = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RmtInf/camt:Ustrd", nsmgr); tr.description = desc != null ? desc.InnerText : String.Empty; XmlNode partnerName = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:Dbtr/camt:Nm", nsmgr); if (partnerName != null) { tr.partnerName = partnerName.InnerText; } XmlNode accountCode = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:DbtrAcct/camt:Id/camt:IBAN", nsmgr); if (accountCode != null) { tr.accountCode = accountCode.InnerText; } XmlNode CrdtName = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:Cdtr/camt:Nm", nsmgr); XmlNode DbtrName = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:RltdPties/camt:Dbtr/camt:Nm", nsmgr); if ((CrdtName != null) && (CrdtName.InnerText != ownName)) { if ((DbtrName != null) && (DbtrName.InnerText == ownName)) { // we are the debitor } else if (ownName != String.Empty) { // sometimes donors write the project or recipient in the field where the organisation is supposed to be Log.Write("CrdtName is not like expected: " + tr.description + " --- " + CrdtName.InnerText); tr.description += " " + CrdtName.InnerText; } } XmlNode EndToEndId = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:Refs/camt:EndToEndId", nsmgr); if ((EndToEndId != null) && (EndToEndId.InnerText != "NOTPROVIDED") && !EndToEndId.InnerText.StartsWith("LS-") && !EndToEndId.InnerText.StartsWith("ZV") && !EndToEndId.InnerText.StartsWith("IZV-DISPO")) { // sometimes donors write the project or recipient in unexpected field Log.Write("EndToEndId: " + tr.description + " --- " + EndToEndId.InnerText); tr.description += " " + EndToEndId.InnerText; } // eg NSTO+152+00900. look for SEPA Geschäftsvorfallcodes // see the codes: https://www.wgzbank.de/export/sites/wgzbank/de/wgzbank/downloads/produkte_leistungen/firmenkunden/zv_aktuelles/Uebersicht-GVC-und-Buchungstexte-WGZ-BANK_V062015.pdf string[] GVCCode = nodeEntry.SelectSingleNode("camt:NtryDtls/camt:TxDtls/camt:BkTxCd/camt:Prtry/camt:Cd", nsmgr).InnerText.Split( new char[] { '+' }); tr.typecode = GVCCode[1]; // for SEPA direct debit batches, there are multiple TxDtls records XmlNodeList transactionDetails = nodeEntry.SelectNodes("camt:NtryDtls/camt:TxDtls", nsmgr); if (transactionDetails.Count > 1) { tr.partnerName = String.Empty; tr.description = String.Format("SEPA Sammel-Basislastschrift mit {0} Lastschriften", transactionDetails.Count); } stmt.transactions.Add(tr); Log.Write("count : " + stmt.transactions.Count.ToString()); } statements.Add(stmt); } } catch (Exception e) { throw new Exception( "problem with file " + filename + "; " + e.Message + Environment.NewLine + e.StackTrace); } finally { Thread.CurrentThread.CurrentCulture = backupCulture; } }