gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] 01/02: ISO 20022 amount progress


From: gnunet
Subject: [libeufin] 01/02: ISO 20022 amount progress
Date: Tue, 07 Jul 2020 22:15:13 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository libeufin.

commit f75f5baa9b9672c79275b2d42029a6c614d1c8db
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Tue Jul 7 21:28:36 2020 +0530

    ISO 20022 amount progress
---
 .idea/dictionaries/dold.xml                        |   1 +
 .idea/inspectionProfiles/Project_Default.xml       |   8 +
 nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt |   9 +-
 .../tech/libeufin/nexus/bankaccount/BankAccount.kt |   6 +-
 .../tech/libeufin/nexus/iso20022/Iso20022.kt       | 357 +++++++++++++--------
 nexus/src/test/kotlin/Iso20022Test.kt              |  17 +-
 .../camt.053/de.camt.053.001.02.xml                |   2 +-
 7 files changed, 248 insertions(+), 152 deletions(-)

diff --git a/.idea/dictionaries/dold.xml b/.idea/dictionaries/dold.xml
index f518024..ce7b473 100644
--- a/.idea/dictionaries/dold.xml
+++ b/.idea/dictionaries/dold.xml
@@ -8,6 +8,7 @@
       <w>cronspec</w>
       <w>dbit</w>
       <w>ebics</w>
+      <w>iban</w>
       <w>infos</w>
       <w>libeufin</w>
       <w>payto</w>
diff --git a/.idea/inspectionProfiles/Project_Default.xml 
b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..030f244
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,8 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="JsonStandardCompliance" enabled="true" 
level="ERROR" enabled_by_default="true">
+      <option name="myWarnAboutComments" value="false" />
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
index 7e94947..52ef436 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Taler.kt
@@ -42,7 +42,7 @@ import tech.libeufin.nexus.bankaccount.addPaymentInitiation
 import tech.libeufin.nexus.iso20022.CamtBankAccountEntry
 import tech.libeufin.nexus.iso20022.CreditDebitIndicator
 import tech.libeufin.nexus.iso20022.EntryStatus
-import tech.libeufin.nexus.iso20022.TransactionInfo
+import tech.libeufin.nexus.iso20022.TransactionDetails
 import tech.libeufin.nexus.server.Pain001Data
 import tech.libeufin.nexus.server.authenticateRequest
 import tech.libeufin.nexus.server.expectNonNull
@@ -356,7 +356,7 @@ private suspend fun talerAddIncoming(call: ApplicationCall, 
httpClient: HttpClie
 }
 
 
-private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: 
TransactionInfo) {
+private fun ingestIncoming(payment: NexusBankTransactionEntity, txDtls: 
TransactionDetails) {
     val subject = txDtls.unstructuredRemittanceInformation
     val debtorName = txDtls.debtor?.name
     if (debtorName == null) {
@@ -424,12 +424,13 @@ fun ingestTalerTransactions() {
         }.orderBy(Pair(NexusBankTransactionsTable.id, SortOrder.ASC)).forEach {
             // Incoming payment.
             val tx = jacksonObjectMapper().readValue(it.transactionJson, 
CamtBankAccountEntry::class.java)
-            if (tx.transactionInfos.size != 1) {
+            val txDetails = tx.details
+            if (txDetails == null) {
                 // We don't support batch transactions at the moment!
                 logger.warn("batch transactions not supported")
             } else {
                 when (tx.creditDebitIndicator) {
-                    CreditDebitIndicator.CRDT -> ingestIncoming(it, txDtls = 
tx.transactionInfos[0])
+                    CreditDebitIndicator.CRDT -> ingestIncoming(it, txDtls = 
txDetails)
                 }
             }
             lastId = it.id.value
diff --git 
a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
index 0ee3c80..11644c2 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/bankaccount/BankAccount.kt
@@ -156,15 +156,15 @@ fun processCamtMessage(
             val rawEntity = NexusBankTransactionEntity.new {
                 bankAccount = acct
                 accountTransactionId = "AcctSvcrRef:$acctSvcrRef"
-                amount = tx.entryAmount.value.toPlainString()
-                currency = tx.entryAmount.currency
+                amount = tx.amount.value.toPlainString()
+                currency = tx.amount.currency
                 transactionJson = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx)
                 creditDebitIndicator = tx.creditDebitIndicator.name
                 status = tx.status
             }
             rawEntity.flush()
             if (tx.creditDebitIndicator == CreditDebitIndicator.DBIT) {
-                val t0 = tx.transactionInfos.getOrNull(0)
+                val t0 = tx.details
                 val msgId = t0?.messageId
                 val pmtInfId = t0?.paymentInformationId
                 if (t0 != null && msgId != null && pmtInfId != null) {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
index 2a9fda8..4f8beaf 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt
@@ -91,14 +91,14 @@ data class Balance(
 )
 
 data class CamtParseResult(
-    val reports: List<CamtReport>,
-    val balances: List<Balance>,
-    val messageId: String,
     /**
      * Message type in form of the ISO 20022 message name.
      */
     val messageType: CashManagementResponseType,
-    val creationDateTime: String
+    val messageId: String,
+    val creationDateTime: String,
+    val balances: List<Balance>,
+    val reports: List<CamtReport>
 )
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
@@ -117,7 +117,7 @@ data class OrganizationIdentification(
 
 /**
  * Identification of a party, which can be a private party
- * or an organiation.
+ * or an organization.
  *
  * Mapping of ISO 20022 PartyIdentification135.
  */
@@ -152,10 +152,7 @@ data class CurrencyExchange(
 )
 
 @JsonInclude(JsonInclude.Include.NON_NULL)
-data class TransactionInfo(
-    val batchPaymentInformationId: String?,
-    val batchMessageId: String?,
-
+data class TransactionDetails(
     val debtor: PartyIdentification?,
     val debtorAccount: CashAccount?,
     val debtorAgent: AgentIdentification?,
@@ -167,14 +164,31 @@ data class TransactionInfo(
     val paymentInformationId: String? = null,
     val messageId: String? = null,
 
-    val amount: CurrencyAmount,
-    val creditDebitIndicator: CreditDebitIndicator,
+    /**
+     * Currency exchange information for the transaction's amount.
+     */
+    val currencyExchange: CurrencyExchange?,
 
+    /**
+     * Amount as given in the payment initiation.
+     * Can be same or different currency as account currency.
+     */
     val instructedAmount: CurrencyAmount?,
-    val transactionAmount: CurrencyAmount?,
 
-    val instructedAmountCurrencyExchange: CurrencyExchange?,
-    val transactionAmountCurrencyExchange: CurrencyExchange?,
+    /**
+     * Raw amount used for currency exchange, before extra charges.
+     * Can be same or different currency as account currency.
+     */
+    val counterValueAmount: CurrencyAmount?,
+
+    /**
+     * Money that was moved between banks.
+     *
+     * For CH, we use the "TxAmt".
+     * For EPC, this amount is either blank or taken
+     * from the "IBC" proprietary amount.
+     */
+    val interBankSettlementAmount: CurrencyAmount?,
 
     /**
      * Unstructured remittance information (=subject line) of the transaction,
@@ -193,9 +207,28 @@ data class ReturnInfo(
     val additionalInfo: String?
 )
 
+data class BatchTransaction(
+    val amount: CurrencyAmount,
+
+    /**
+     * Is this entry debiting or crediting the account
+     * it is reported for?
+     */
+    val creditDebitIndicator: CreditDebitIndicator,
+
+    val details: TransactionDetails
+)
+
+
+data class Batch(
+    val batchTransactions: List<BatchTransaction>,
+    val messageId: String?,
+    val paymentInformationId: String?
+)
+
 @JsonInclude(JsonInclude.Include.NON_NULL)
 data class CamtBankAccountEntry(
-    val entryAmount: CurrencyAmount,
+    val amount: CurrencyAmount,
 
     /**
      * Is this entry debiting or crediting the account
@@ -212,18 +245,33 @@ data class CamtBankAccountEntry(
      * Code that describes the type of bank transaction
      * in more detail
      */
-
     val bankTransactionCode: String,
-    /**
-     * Transaction details, if this entry contains a single transaction.
-     */
-    val transactionInfos: List<TransactionInfo>,
+
     val valueDate: String?,
+
     val bookingDate: String?,
+
     val accountServicerRef: String?,
-    val entryRef: String?
-)
 
+    val entryRef: String?,
+
+    /**
+     * Currency exchange information for the entry's amount.
+     * Only present if currency exchange happened at the entry level.
+     */
+    val currencyExchange: CurrencyExchange?,
+
+    /**
+     * Value before/after currency exchange before charges have been applied.
+     */
+    val counterValueAmount: CurrencyAmount?,
+
+    /**
+     * Details of the underlying transaction for type=Simple.
+     */
+    val details: TransactionDetails?,
+    val batches: List<Batch>?
+)
 
 class CamtParsingError(msg: String) : Exception(msg)
 
@@ -434,10 +482,10 @@ private fun XmlElementDestructor.extractParty(): 
PartyIdentification {
         maybeUniqueChildNamed("PrvtId") {
             maybeUniqueChildNamed("DtAndPlcOfBirth") {
                 PrivateIdentification(
-                    birthDate = maybeUniqueChildNamed("BirthDt") { 
it.textContent},
-                    cityOfBirth = maybeUniqueChildNamed("CityOfBirth") { 
it.textContent},
-                    countryOfBirth = maybeUniqueChildNamed("CtryOfBirth") { 
it.textContent},
-                    provinceOfBirth = maybeUniqueChildNamed("PrvcOfBirth") { 
it.textContent}
+                    birthDate = maybeUniqueChildNamed("BirthDt") { 
it.textContent },
+                    cityOfBirth = maybeUniqueChildNamed("CityOfBirth") { 
it.textContent },
+                    countryOfBirth = maybeUniqueChildNamed("CtryOfBirth") { 
it.textContent },
+                    provinceOfBirth = maybeUniqueChildNamed("PrvcOfBirth") { 
it.textContent }
                 )
             }
         }
@@ -446,13 +494,13 @@ private fun XmlElementDestructor.extractParty(): 
PartyIdentification {
     val organizationId = maybeUniqueChildNamed("Id") {
         maybeUniqueChildNamed("OrgId") {
             OrganizationIdentification(
-                bic = maybeUniqueChildNamed("BICOrBEI") { it.textContent} ?: 
maybeUniqueChildNamed("AnyBIC") { it.textContent},
-                lei = maybeUniqueChildNamed("LEI") { it.textContent}
+                bic = maybeUniqueChildNamed("BICOrBEI") { it.textContent }
+                    ?: maybeUniqueChildNamed("AnyBIC") { it.textContent },
+                lei = maybeUniqueChildNamed("LEI") { it.textContent }
             )
         }
     }
 
-
     return PartyIdentification(
         name = maybeUniqueChildNamed("Nm") { it.textContent },
         otherId = otherId,
@@ -482,7 +530,7 @@ private fun 
XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang
     return maybeUniqueChildNamed("CcyXchg") {
         CurrencyExchange(
             sourceCurrency = requireUniqueChildNamed("SrcCcy") { 
it.textContent },
-            targetCurrency = requireUniqueChildNamed("TgtCcy") { 
it.textContent },
+            targetCurrency = requireUniqueChildNamed("TrgtCcy") { 
it.textContent },
             contractId = maybeUniqueChildNamed("CtrctId") { it.textContent },
             exchangeRate = requireUniqueChildNamed("XchgRate") { 
it.textContent },
             quotationDate = maybeUniqueChildNamed("QtnDt") { it.textContent },
@@ -491,108 +539,113 @@ private fun 
XmlElementDestructor.extractMaybeCurrencyExchange(): CurrencyExchang
     }
 }
 
-
-private fun XmlElementDestructor.extractTransactionInfos(
+private fun XmlElementDestructor.extractBatches(
     outerAmount: CurrencyAmount,
     outerCreditDebitIndicator: CreditDebitIndicator
-): List<TransactionInfo> {
-
-    val numTxDtls = requireUniqueChildNamed("NtryDtls") {
-        mapEachChildNamed("TxDtls") { Unit }
-    }.count()
+): List<Batch> {
+    return mapEachChildNamed("NtryDtls") {
+        val numDtls = mapEachChildNamed("TxDtls") { Unit }.count()
+        var amount = maybeExtractCurrencyAmount()
+        var creditDebitIndicator = maybeExtractCreditDebitIndicator()
+
+        if (amount == null && numDtls == 1) {
+            amount = outerAmount
+            creditDebitIndicator = outerCreditDebitIndicator
+        }
+        if (amount == null || creditDebitIndicator == null) {
+            throw Error("no amount for inner transaction")
+        }
+        val txs = mapEachChildNamed("TxDtls") {
+            val details = extractTransactionDetails(outerAmount, 
outerCreditDebitIndicator, false)
+            BatchTransaction(amount, creditDebitIndicator, details)
+        }
+        Batch(txs, null, null)
+    }
+}
 
-    return requireUniqueChildNamed("NtryDtls") {
-        mapEachChildNamed("TxDtls") {
+private fun XmlElementDestructor.maybeExtractCreditDebitIndicator(): 
CreditDebitIndicator? {
+    return maybeUniqueChildNamed("CdtDbtInd") { it.textContent }?.let {
+        CreditDebitIndicator.valueOf(it)
+    }
+}
 
-            val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
-                maybeUniqueChildNamed("InstrAmt") { extractCurrencyAmount() }
-            }
+private fun XmlElementDestructor.extractTransactionDetails(
+    outerAmount: CurrencyAmount,
+    outerCreditDebitIndicator: CreditDebitIndicator,
+    batch: Boolean
+): TransactionDetails {
+    val instructedAmount = maybeUniqueChildNamed("AmtDtls") {
+        maybeUniqueChildNamed("InstdAmt") { extractCurrencyAmount() }
+    }
 
-            val transactionAmount = maybeUniqueChildNamed("AmtDtls") {
-                maybeUniqueChildNamed("TxAmt") { extractCurrencyAmount() }
-            }
+    val creditDebitIndicator = maybeExtractCreditDebitIndicator() ?: 
outerCreditDebitIndicator
 
-            var amount = maybeExtractCurrencyAmount()
-            var creditDebitIndicator = maybeUniqueChildNamed("CdtDbtInd") { 
it.textContent }?.let {
-                CreditDebitIndicator.valueOf(it)
-            }
-            if (amount == null) {
-                when {
-                    numTxDtls == 1 -> {
-                        amount = outerAmount
-                        creditDebitIndicator = outerCreditDebitIndicator
-                    }
-                    transactionAmount?.currency == outerAmount.currency -> {
-                        amount = transactionAmount
-                        creditDebitIndicator = outerCreditDebitIndicator
-                    }
-                    instructedAmount?.currency == outerAmount.currency -> {
-                        amount = instructedAmount
-                        creditDebitIndicator = outerCreditDebitIndicator
-                    }
-                    else -> {
-                        throw Error("invalid camt, no amount for transaction 
details of entry details")
-                    }
-                }
-            }
+    val currencyExchange = maybeUniqueChildNamed("AmtDtls") {
+        val cxCntrVal =  maybeUniqueChildNamed("CntrValAmt") { 
extractMaybeCurrencyExchange() }
+        val cxTx =  maybeUniqueChildNamed("TxAmt") { 
extractMaybeCurrencyExchange() }
+        val cxInstr = maybeUniqueChildNamed("InstdAmt") { 
extractMaybeCurrencyExchange() }
+        cxCntrVal ?: cxTx ?: cxInstr
+    }
 
-            if (creditDebitIndicator == null) {
-                throw Error("invalid camt, no credit/debit indicator for 
transaction details of entry details")
+    return TransactionDetails(
+        instructedAmount = instructedAmount,
+        counterValueAmount = maybeUniqueChildNamed("AmtDtls") {
+            maybeUniqueChildNamed("CntrValAmt") { extractCurrencyAmount() }
+        },
+        currencyExchange = currencyExchange,
+        // FIXME: implement
+        interBankSettlementAmount = null,
+        endToEndId = maybeUniqueChildNamed("Refs") {
+            maybeUniqueChildNamed("EndToEndId") { it.textContent }
+        },
+        messageId = maybeUniqueChildNamed("Refs") {
+            maybeUniqueChildNamed("MsgId") { it.textContent }
+        },
+        paymentInformationId = maybeUniqueChildNamed("Refs") {
+            maybeUniqueChildNamed("PmtInfId") { it.textContent }
+        },
+        unstructuredRemittanceInformation = maybeUniqueChildNamed("RmtInf") {
+            val chunks = mapEachChildNamed("Ustrd", { it.textContent })
+            if (chunks.isEmpty()) {
+                null
+            } else {
+                chunks.joinToString(separator = "")
             }
-
-            TransactionInfo(
-                batchMessageId = null,
-                batchPaymentInformationId = null,
-                amount = amount,
-                creditDebitIndicator = creditDebitIndicator,
-                instructedAmount = instructedAmount,
-                instructedAmountCurrencyExchange = 
maybeUniqueChildNamed("AmtDtls") {
-                    maybeUniqueChildNamed("InstrAmt") { 
extractMaybeCurrencyExchange() }
-                },
-                transactionAmount = transactionAmount,
-                transactionAmountCurrencyExchange = 
maybeUniqueChildNamed("AmtDtls") {
-                    maybeUniqueChildNamed("TxAmt") { 
extractMaybeCurrencyExchange() }
-                },
-                endToEndId = maybeUniqueChildNamed("Refs") {
-                    maybeUniqueChildNamed("EndToEndId") { it.textContent }
-                },
-                messageId = maybeUniqueChildNamed("Refs") {
-                    maybeUniqueChildNamed("MsgId") { it.textContent }
-                },
-                paymentInformationId = maybeUniqueChildNamed("Refs") {
-                    maybeUniqueChildNamed("PmtInfId") { it.textContent }
-                },
-                unstructuredRemittanceInformation = 
maybeUniqueChildNamed("RmtInf") {
-                    val chunks = mapEachChildNamed("Ustrd", { it.textContent })
-                    if (chunks.isEmpty()) {
-                        null
-                    } else {
-                        chunks.joinToString(separator = "")
-                    }
-                } ?: "",
-                creditorAgent = maybeUniqueChildNamed("CdtrAgt") { 
extractAgent() },
-                debtorAgent = maybeUniqueChildNamed("DbtrAgt") { 
extractAgent() },
-                debtorAccount = maybeUniqueChildNamed("DbtrAgt") { 
extractAccount() },
-                creditorAccount = maybeUniqueChildNamed("CdtrAgt") { 
extractAccount() },
-                debtor = maybeUniqueChildNamed("Dbtr") { extractParty() },
-                creditor = maybeUniqueChildNamed("Cdtr") { extractParty() },
-                returnInfo = maybeUniqueChildNamed("RtrInf") {
-                    ReturnInfo(
-                        originalBankTransactionCode = 
maybeUniqueChildNamed("OrgnlBkTxCd") {
-                            extractInnerBkTxCd(
-                                when (creditDebitIndicator) {
-                                    CreditDebitIndicator.DBIT -> 
CreditDebitIndicator.CRDT
-                                    CreditDebitIndicator.CRDT -> 
CreditDebitIndicator.DBIT
-                                })
-                        },
-                        originator = maybeUniqueChildNamed("Orgtr") { 
extractParty() },
-                        reason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
-                        proprietaryReason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
-                        additionalInfo = maybeUniqueChildNamed("AddtlInf") { 
it.textContent }
+        } ?: "",
+        creditorAgent = maybeUniqueChildNamed("RltdAgts") { 
maybeUniqueChildNamed("CdtrAgt") { extractAgent() } },
+        debtorAgent = maybeUniqueChildNamed("RltdAgts") { 
maybeUniqueChildNamed("DbtrAgt") { extractAgent() } },
+        debtorAccount = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("DbtrAgt") { extractAccount() } },
+        creditorAccount = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("CdtrAgt") { extractAccount() } },
+        debtor = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("Dbtr") { extractParty() } },
+        creditor = maybeUniqueChildNamed("RltdPties") { 
maybeUniqueChildNamed("Cdtr") { extractParty() } },
+        returnInfo = maybeUniqueChildNamed("RtrInf") {
+            ReturnInfo(
+                originalBankTransactionCode = 
maybeUniqueChildNamed("OrgnlBkTxCd") {
+                    extractInnerBkTxCd(
+                        when (creditDebitIndicator) {
+                            CreditDebitIndicator.DBIT -> 
CreditDebitIndicator.CRDT
+                            CreditDebitIndicator.CRDT -> 
CreditDebitIndicator.DBIT
+                        }
                     )
-                }
+                },
+                originator = maybeUniqueChildNamed("Orgtr") { extractParty() },
+                reason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Cd") { it.textContent } },
+                proprietaryReason = maybeUniqueChildNamed("Rsn") { 
maybeUniqueChildNamed("Prtry") { it.textContent } },
+                additionalInfo = maybeUniqueChildNamed("AddtlInf") { 
it.textContent }
             )
         }
+    )
+}
+
+
+private fun XmlElementDestructor.extractSingleDetails(
+    outerAmount: CurrencyAmount,
+    outerCreditDebitIndicator: CreditDebitIndicator
+): TransactionDetails {
+    return requireUniqueChildNamed("NtryDtls") {
+        requireUniqueChildNamed("TxDtls") {
+            extractTransactionDetails(outerAmount, outerCreditDebitIndicator, 
false)
+        }
     }
 }
 
@@ -600,21 +653,21 @@ private fun 
XmlElementDestructor.extractInnerBkTxCd(creditDebitIndicator: Credit
 
     val domain = maybeUniqueChildNamed("Domn") { maybeUniqueChildNamed("Cd") { 
it.textContent } }
     val family = maybeUniqueChildNamed("Domn") {
-            maybeUniqueChildNamed("Fmly") {
-                maybeUniqueChildNamed("Cd") { it.textContent }
-            }
+        maybeUniqueChildNamed("Fmly") {
+            maybeUniqueChildNamed("Cd") { it.textContent }
         }
+    }
     val subfamily = maybeUniqueChildNamed("Domn") {
-            maybeUniqueChildNamed("Fmly") {
-                maybeUniqueChildNamed("SubFmlyCd") { it.textContent }
-            }
+        maybeUniqueChildNamed("Fmly") {
+            maybeUniqueChildNamed("SubFmlyCd") { it.textContent }
         }
+    }
     val proprietaryCode = maybeUniqueChildNamed("Prtry") {
-            maybeUniqueChildNamed("Cd") { it.textContent }
-        }
+        maybeUniqueChildNamed("Cd") { it.textContent }
+    }
     val proprietaryIssuer = maybeUniqueChildNamed("Prtry") {
-            maybeUniqueChildNamed("Issr") { it.textContent }
-        }
+        maybeUniqueChildNamed("Issr") { it.textContent }
+    }
 
     if (domain != null && family != null && subfamily != null) {
         return "$domain-$family-$subfamily"
@@ -631,6 +684,7 @@ private fun 
XmlElementDestructor.extractInnerBkTxCd(creditDebitIndicator: Credit
     return "XTND-NTAV-NTAV"
 }
 
+
 private fun XmlElementDestructor.extractInnerTransactions(): CamtReport {
     val account = requireUniqueChildNamed("Acct") { extractAccount() }
     val entries = mapEachChildNamed("Ntry") {
@@ -646,14 +700,41 @@ private fun 
XmlElementDestructor.extractInnerTransactions(): CamtReport {
         }
         val acctSvcrRef = maybeUniqueChildNamed("AcctSvcrRef") { 
it.textContent }
         val entryRef = maybeUniqueChildNamed("NtryRef") { it.textContent }
+
+        val numNtryDtls = mapEachChildNamed("NtryDtls") {
+            Unit
+        }.count()
+
+        val currencyExchange = maybeUniqueChildNamed("AmtDtls") {
+            val cxCntrVal =  maybeUniqueChildNamed("CntrValAmt") { 
extractMaybeCurrencyExchange() }
+            val cxTx =  maybeUniqueChildNamed("TxAmt") { 
extractMaybeCurrencyExchange() }
+            val cxInstr = maybeUniqueChildNamed("InstrAmt") { 
extractMaybeCurrencyExchange() }
+            cxCntrVal ?: cxTx ?: cxInstr
+        }
+
+        val counterValueAmount = maybeUniqueChildNamed("AmtDtls") {
+            maybeUniqueChildNamed("CntrValAmt") { extractCurrencyAmount() }
+        }
+
         // For now, only support account servicer reference as id
-        val transactionInfos = extractTransactionInfos(amount, 
creditDebitIndicator)
+
         CamtBankAccountEntry(
-            entryAmount = amount,
+            amount = amount,
             status = status,
+            currencyExchange = currencyExchange,
+            counterValueAmount = counterValueAmount,
             creditDebitIndicator = creditDebitIndicator,
             bankTransactionCode = btc,
-            transactionInfos = transactionInfos,
+            details = if (numNtryDtls == 1) {
+                extractSingleDetails(amount, creditDebitIndicator)
+            } else {
+                null
+            },
+            batches = if (numNtryDtls > 1) {
+                extractBatches(amount, creditDebitIndicator)
+            } else {
+                null
+            },
             bookingDate = maybeUniqueChildNamed("BookgDt") { 
extractDateOrDateTime() },
             valueDate = maybeUniqueChildNamed("ValDt") { 
extractDateOrDateTime() },
             accountServicerRef = acctSvcrRef,
@@ -723,7 +804,13 @@ fun parseCamtMessage(doc: Document): CamtParseResult {
                     }
                 }
             }
-            CamtParseResult(reports, balances, messageId, messageType, 
creationDateTime)
+            CamtParseResult(
+                reports = reports,
+                balances = balances,
+                messageId = messageId,
+                messageType = messageType,
+                creationDateTime = creationDateTime
+            )
         }
     }
 }
diff --git a/nexus/src/test/kotlin/Iso20022Test.kt 
b/nexus/src/test/kotlin/Iso20022Test.kt
index 78c2f14..2e266fb 100644
--- a/nexus/src/test/kotlin/Iso20022Test.kt
+++ b/nexus/src/test/kotlin/Iso20022Test.kt
@@ -6,6 +6,7 @@ import tech.libeufin.nexus.iso20022.*
 import tech.libeufin.util.XMLUtil
 import java.math.BigDecimal
 import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 
 fun loadXmlResource(name: String): Document {
@@ -28,31 +29,29 @@ class Iso20022Test {
         assertEquals(1, r.reports.size)
 
         // First Entry
-        
assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].entryAmount.value) 
== 0)
-        assertEquals("EUR", r.reports[0].entries[0].entryAmount.currency)
+        
assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].amount.value) == 0)
+        assertEquals("EUR", r.reports[0].entries[0].amount.currency)
         assertEquals(CreditDebitIndicator.CRDT, 
r.reports[0].entries[0].creditDebitIndicator)
         assertEquals(EntryStatus.BOOK, r.reports[0].entries[0].status)
         assertEquals(null, r.reports[0].entries[0].entryRef)
         assertEquals("acctsvcrref-001", 
r.reports[0].entries[0].accountServicerRef)
         assertEquals("PMNT-RCDT-ESCT", 
r.reports[0].entries[0].bankTransactionCode)
-        assertEquals(1, r.reports[0].entries[0].transactionInfos.size)
-        assertEquals("EUR", 
r.reports[0].entries[0].transactionInfos[0].amount.currency)
-        
assertTrue(BigDecimal(100).compareTo(r.reports[0].entries[0].transactionInfos[0].amount.value)
 == 0)
-        assertEquals(CreditDebitIndicator.CRDT, 
r.reports[0].entries[0].transactionInfos[0].creditDebitIndicator)
-        assertEquals("unstructured info one", 
r.reports[0].entries[0].transactionInfos[0].unstructuredRemittanceInformation)
+        assertNotNull(r.reports[0].entries[0].details)
+        assertEquals("unstructured info one", 
r.reports[0].entries[0].details?.unstructuredRemittanceInformation)
 
         // Second Entry
-        assertEquals("unstructured info across lines", 
r.reports[0].entries[1].transactionInfos[0].unstructuredRemittanceInformation)
+        assertEquals("unstructured info across lines", 
r.reports[0].entries[1].details?.unstructuredRemittanceInformation)
 
         // Third Entry
 
         // Make sure that round-tripping of entry CamtBankAccountEntry JSON 
works
         for (entry in r.reports.flatMap { it.entries }) {
             val txStr = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(entry)
-            println(txStr)
             val tx2 = jacksonObjectMapper().readValue(txStr, 
CamtBankAccountEntry::class.java)
             val tx2Str = 
jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(tx2)
             assertEquals(jacksonObjectMapper().readTree(txStr), 
jacksonObjectMapper().readTree(tx2Str))
         }
+
+        
println(jacksonObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(r))
     }
 }
diff --git 
a/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml 
b/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml
index d297f47..dc3511e 100644
--- a/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml
+++ b/nexus/src/test/resources/iso20022-samples/camt.053/de.camt.053.001.02.xml
@@ -361,4 +361,4 @@
             </Ntry>
         </Stmt>
     </BkToCstmrStmt>
-</Document>
\ No newline at end of file
+</Document>

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]