commit bc1b7a3ee37698bcac5979b180062886feeb626d
parent a6ad71bb7054b84ab4b8f141cb89265b7c37dede
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date: Tue, 17 Mar 2020 17:05:27 +0100
Camt generation.
Avoid spreading the logic through multiple helper
functions. And also, separaing the data retrieval
from database and the actual (CAMT) string construction.
Diffstat:
2 files changed, 120 insertions(+), 146 deletions(-)
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -136,19 +136,118 @@ private suspend fun ApplicationCall.respondEbicsKeyManagement(
respondText(text, ContentType.Application.Xml, HttpStatusCode.OK)
}
+private fun buildCamtString(history: SizedIterable<BankTransactionEntity>, type: Int): String {
+
+ return constructXml(indent = true) {
+ root("Document") {
+ attribute("xmlns", "urn:iso:std:iso:20022:tech:xsd:camt.053.001.08")
+ attribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
+ attribute("xmlns:schemaLocation", "urn:iso:std:iso:20022:tech:xsd:camt.053.001.02 camt.053.001.02.xsd")
+ element("BkToCstmrStmt") {
+ element("GrpHdr") {
+ element("MsgId") {
+ // unique identifier for a message
+ text("id under group header")
+ }
+ element("CreDtTm")
+ }
+ element(if (type == 52) "Rpt" else "Stmt") {
+ element("Id") {
+ // unique identificator for a report.
+ text("id under report")
+ }
+ element("Acct") {
+ // mandatory account identifier
+ text("account identifier")
+ }
+ element("Bal") {
+ element("Tp") {
+ // FIXME: type
+ element("CdOrPrTry") {
+ /**
+ * FIXME: code-or-proprietary
+ * This section specifies the 'balance type', either in a
+ * 'coded' format or in a proprietary one.
+ */
+ }
+ }
+ element("Amt") {
+ /**
+ * FIXME: Amount
+ */
+ attribute("Ccy", "EUR")
+ text(Amount("0.99").toPlainString())
+ }
+ element("CdtDbtInd") {
+ /**
+ * FIXME: credit-debit-indicator
+ * Indicates whether the balance is a 'credit' ("CRDT") or a 'debit' ("DBIT") balance.
+ */
+ }
+ element("Dt") {
+ /**
+ * FIXME: date, in YYYY-MM-DD format
+ */
+ }
+ }
+
+ history.forEach {
+ element("Ntry") {
+ /* FIXME: one entry in an account history.
+ * NOTE: this element can appear from 0 to unbounded number of times.
+ * */
+ element("Amt") {
+ /* FIXME: amount of this entry */
+ }
+ element("CdtDbtInd") {
+ /* FIXME: as above, whether the entry witnesses debit or credit */
+ }
+ element("Sts") {
+ /* FIXME: status of the entry (see 2.4.2.15.5 from the ISO20022 reference document.)
+ *
+ * From the original text:
+ * "Status of an entry on the books of the account servicer"
+ */
+ }
+ element("BkTxCd") {
+ /* FIXME: Bank-transaction-code, see section 2.4.2.15.10.
+ * From the original text:
+ *
+ * "Set of elements used to fully identify the type of underlying
+ * transaction resulting in an entry"
+ */
+ }
+ element("BookgDt") {
+ /**
+ * FIXME, Booking-date: when the entry was posted on the books
+ * of the account servicer; do not necessarily implies that assets
+ * become available. NOTE: this element is optional.
+ */
+ }
+ element("ValDt") {
+ /**
+ * FIXME, Value-date: when the asset corresponding to one entry
+ * becomes available (or unavailable, in case of debit type entry)
+ * to the account owner. NOTE: this element is optional.
+ */
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
/**
- * This function populates the "history" content of both a CAMT.052 and CAMT.053.
- * FIXME: There might be needed some filter to exclude non-booked entries from a
- * query-set (as CAMT.053 should do, see #6046)
- *
- * @param cusromerId unique identifier of a bank's customer: not EBICS-relevant.
- * @param request the EBICS request carrying a "history" message.
- * @param base the sub-node where to start attaching history elements.
+ * Builds CAMT response.
*
+ * @param history the list of all the history elements
+ * @param type 52 or 53.
*/
-private fun iterHistory(customerId: Int, header: EbicsRequest.Header, base: XmlElementBuilder) {
+private fun constructCamtResponse(type: Int, customerId: Int, header: EbicsRequest.Header): String {
- extractHistoryForEach(
+ val history = extractHistory(
customerId,
try {
(header.static.orderDetails?.orderParams as EbicsRequest.StandardOrderParams).dateRange!!.start.toString()
@@ -162,125 +261,8 @@ private fun iterHistory(customerId: Int, header: EbicsRequest.Header, base: XmlE
LOGGER.debug("Asked to iterate over history with NO end date; default to now")
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()).toString()
}
- ) {
-
- base.element("Ntry") {
- /* FIXME: one entry in an account history.
- * NOTE: this element can appear from 0 to unbounded number of times.
- * */
- element("Amt") {
- /* FIXME: amount of this entry */
- }
- element("CdtDbtInd") {
- /* FIXME: as above, whether the entry witnesses debit or credit */
- }
- element("Sts") {
- /* FIXME: status of the entry (see 2.4.2.15.5 from the ISO20022 reference document.)
- *
- * From the original text:
- * "Status of an entry on the books of the account servicer"
- */
- }
- element("BkTxCd") {
- /* FIXME: Bank-transaction-code, see section 2.4.2.15.10.
- * From the original text:
- *
- * "Set of elements used to fully identify the type of underlying
- * transaction resulting in an entry"
- */
- }
- element("BookgDt") {
- /**
- * FIXME, Booking-date: when the entry was posted on the books
- * of the account servicer; do not necessarily implies that assets
- * become available. NOTE: this element is optional.
- */
- }
- element("ValDt") {
- /**
- * FIXME, Value-date: when the asset corresponding to one entry
- * becomes available (or unavailable, in case of debit type entry)
- * to the account owner. NOTE: this element is optional.
- */
- }
- }
- }
-}
-
-/**
- * This function populates the content under "Rpt" or "Stmt" nodes,
- * therefore is valid for generating both C52 and C53 responses.
- *
- * @param base the sub-node where starting to append content.
- */
-private fun balance(base: XmlElementBuilder) {
-
- base.element("Id") {
- // unique identificator for a report.
- text("id under report")
- }
- base.element("Acct") {
- // mandatory account identifier
- text("account identifier")
- }
- base.element("Bal") {
- element("Tp") {
- // FIXME: type
- element("CdOrPrTry") {
- /**
- * FIXME: code-or-proprietary
- * This section specifies the 'balance type', either in a
- * 'coded' format or in a proprietary one.
- */
- }
- }
- element("Amt") {
- /**
- * FIXME: Amount
- */
- attribute("Ccy", "EUR")
- BigDecimal("1.00")
- }
- element("CdtDbtInd") {
- /**
- * FIXME: credit-debit-indicator
- * Indicates whether the balance is a 'credit' ("CRDT") or a 'debit' ("DBIT") balance.
- */
- }
- element("Dt") {
- /**
- * FIXME: date, in YYYY-MM-DD format
- */
- }
- }
-}
-
-/**
- * Builds CAMT response.
- *
- * @param history the list of all the history elements
- * @param type 52 or 53.
- */
-private fun constructCamtResponse(type: Int, customerId: Int, header: EbicsRequest.Header): String {
- val camt = constructXml(indent = true) {
- root("Document") {
- attribute("xmlns", "urn:iso:std:iso:20022:tech:xsd:camt.053.001.08")
- element("BkToCstmrAcctRpt") {
- element("GrpHdr") {
- element("MsgId") {
- // unique identifier for a message
- text("id under group header")
- }
- }
- element(if (type == 52) "Rpt" else "Stmt") {
-
- balance(this)
- iterHistory(customerId, header, this)
- }
- }
- }
- }
- return camt
+ )
+ return buildCamtString(history, type)
}
@@ -299,7 +281,7 @@ private fun handleEbicsC52(requestContext: RequestContext): ByteArray {
val baos = ByteArrayOutputStream()
val asf = ArchiveStreamFactory().createArchiveOutputStream(ArchiveStreamFactory.ZIP, baos)
- val zae = ZipArchiveEntry("Singleton C53 Entry")
+ val zae = ZipArchiveEntry("Singleton C5{2,3} Entry")
asf.putArchiveEntry(zae)
val bais = ByteArrayInputStream(camt.toByteArray())
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -38,10 +38,7 @@ import io.ktor.routing.post
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
-import org.jetbrains.exposed.sql.Date
-import org.jetbrains.exposed.sql.StdOutSqlLogger
-import org.jetbrains.exposed.sql.addLogger
-import org.jetbrains.exposed.sql.and
+import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
import org.joda.time.DateTime
import org.slf4j.Logger
@@ -163,29 +160,23 @@ fun sampleData() {
}
/**
- * Finds the history for a customer, given dates of 'value'. This date
- * specifies the point in time where a operation settled. The other type
- * of date is 'operation', that indicates the point in time where the
- * transaction was created in the bank's system.
- *
* @param id the customer whose history must be returned. This
* id is local to the bank and is not reused/encoded into other
* EBICS id values.
- * @param builder lambda function that will loop over all the results.
+ *
+ * @return result set of all the operations related to the customer
+ * identified by @p id.
*/
-fun extractHistoryForEach(id: Int, start: String?, end: String?, builder: (BankTransactionEntity) -> Any) {
+fun extractHistory(id: Int, start: String?, end: String?): SizedIterable<BankTransactionEntity> {
val s = if (start != null) DateTime.parse(start) else DateTime(0)
val e = if (end != null) DateTime.parse(end) else DateTime.now()
LOGGER.debug("Fetching history from $s to $e")
- transaction {
+ return transaction {
addLogger(StdOutSqlLogger)
BankTransactionEntity.find {
BankTransactionsTable.localCustomer eq id and BankTransactionsTable.valueDate.between(s.millis, e.millis)
- }.forEach {
- LOGGER.debug("Found history element: $it")
- builder(it)
}
}
}
@@ -244,8 +235,9 @@ fun main() {
val req = call.receive<CustomerHistoryRequest>()
val customer = findCustomer(call.parameters["id"])
val ret = CustomerHistoryResponse()
-
- extractHistoryForEach(customer.id.value, req.start, req.end) {
+ val history = extractHistory(customer.id.value, req.start, req.end)
+
+ history.forEach {
ret.history.add(
CustomerHistoryResponseElement(
subject = it.subject,