From fa169bb5d425df642ac9a9dc81150ced04cd52f8 Mon Sep 17 00:00:00 2001 From: Arnold Galovics Date: Wed, 7 Sep 2022 21:29:35 +0200 Subject: [PATCH] FINERACT-1694: Documentation for Reliable event framework --- .github/workflows/build-documentation.yml | 32 ++ .github/workflows/build-mariadb.yml | 25 +- .github/workflows/build-mysql.yml | 25 +- .github/workflows/build-postgresql.yml | 25 +- fineract-avro-schemas/build.gradle | 2 +- .../src/main/avro/MessageV1.avsc | 5 - .../main/avro/loan/v1/LoanAccountDataV1.avsc | 8 + fineract-client/build.gradle | 2 +- .../reliable-event-framework.adoc | 442 ++++++++++++++++++ fineract-doc/src/docs/en/fineract-client.adoc | 6 + .../images/reliable-event-framework-hla.png | Bin 0 -> 33122 bytes fineract-doc/src/docs/en/index.adoc | 2 +- .../loanaccount/data/LoanAccountData.java | 425 ++++++++--------- .../service/LoanReadPlatformServiceImpl.java | 9 +- 14 files changed, 755 insertions(+), 253 deletions(-) create mode 100644 .github/workflows/build-documentation.yml create mode 100644 fineract-doc/src/docs/en/chapters/architecture/reliable-event-framework.adoc create mode 100644 fineract-doc/src/docs/en/fineract-client.adoc create mode 100644 fineract-doc/src/docs/en/images/reliable-event-framework-hla.png diff --git a/.github/workflows/build-documentation.yml b/.github/workflows/build-documentation.yml new file mode 100644 index 000000000..1cb74fa3f --- /dev/null +++ b/.github/workflows/build-documentation.yml @@ -0,0 +1,32 @@ +name: Fineract Documentation build +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 + - name: Set up JDK 17 + uses: actions/setup-java@2c7a4878f5d120bd643426d54ae1209b29cc01a3 # tag=v3 + with: + java-version: '17' + distribution: 'zulu' + cache: gradle + - uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3 + with: + node-version: 16 + - name: Congfigure vega-cli + run: npm i -g vega-cli --unsafe + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Install additional software + run: | + sudo apt-get update + sudo apt-get install ghostscript graphviz -y + + - name: Documentation build + run: ./gradlew --no-daemon --console=plain doc diff --git a/.github/workflows/build-mariadb.yml b/.github/workflows/build-mariadb.yml index c84dbefca..5c7b1b304 100644 --- a/.github/workflows/build-mariadb.yml +++ b/.github/workflows/build-mariadb.yml @@ -1,4 +1,4 @@ -name: Fineract Gradle build - MariaDB +name: Fineract Build & Test - MariaDB on: [push, pull_request] permissions: @@ -57,25 +57,28 @@ jobs: sudo apt-get update sudo apt-get install ghostscript graphviz -y - - name: Basic Auth Build & Test - run: ./gradlew --no-daemon --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test - - - name: 2FA Build & Test - run: ./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast - - - name: OAuth2 Build & Test - run: ./gradlew --no-daemon --console=plain :oauth2-tests:test --fail-fast + - name: Build & Test + run: | + ./gradlew --no-daemon --console=plain build test --fail-fast -x doc -x :twofactor-tests:test -x :oauth2-test:test + ./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast + ./gradlew --no-daemon --console=plain :oauth2-tests:test --fail-fast - name: Archive test results if: always() uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 with: name: test-results - path: integration-tests/build/reports/ + path: | + integration-tests/build/reports/ + twofactor-tests/build/reports/ + oauth2-tests/build/reports/ - name: Archive server logs if: always() uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 with: name: server-logs - path: integration-tests/build/cargo/ + path: | + integration-tests/build/cargo/ + twofactor-tests/build/cargo/ + oauth2-tests/build/cargo/ diff --git a/.github/workflows/build-mysql.yml b/.github/workflows/build-mysql.yml index d2fb2c6fa..337a76c69 100644 --- a/.github/workflows/build-mysql.yml +++ b/.github/workflows/build-mysql.yml @@ -1,4 +1,4 @@ -name: Fineract Gradle build - MySQL +name: Fineract Build & Test - MySQL on: [push, pull_request] permissions: @@ -57,25 +57,28 @@ jobs: sudo apt-get update sudo apt-get install ghostscript graphviz -y - - name: Basic Auth Build & Test - run: ./gradlew --no-daemon --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=mysql - - - name: 2FA Build & Test - run: ./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast -PdbType=mysql - - - name: OAuth2 Build & Test - run: ./gradlew --no-daemon --console=plain :oauth2-tests:test --fail-fast -PdbType=mysql + - name: Build & Test + run: | + ./gradlew --no-daemon --console=plain build test --fail-fast -x doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=mysql + ./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast -PdbType=mysql + ./gradlew --no-daemon --console=plain :oauth2-tests:test --fail-fast -PdbType=mysql - name: Archive test results if: always() uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 with: name: test-results - path: integration-tests/build/reports/ + path: | + integration-tests/build/reports/ + twofactor-tests/build/reports/ + oauth2-tests/build/reports/ - name: Archive server logs if: always() uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 with: name: server-logs - path: integration-tests/build/cargo/ + path: | + integration-tests/build/cargo/ + twofactor-tests/build/cargo/ + oauth2-tests/build/cargo/ diff --git a/.github/workflows/build-postgresql.yml b/.github/workflows/build-postgresql.yml index d09e68c04..efbf086b0 100644 --- a/.github/workflows/build-postgresql.yml +++ b/.github/workflows/build-postgresql.yml @@ -1,4 +1,4 @@ -name: Fineract Gradle build - PostgreSQL +name: Fineract Build & Test - PostgreSQL on: [push, pull_request] permissions: @@ -58,25 +58,28 @@ jobs: sudo apt-get update sudo apt-get install ghostscript graphviz -y - - name: Basic Auth Build & Test - run: ./gradlew --no-daemon --console=plain build test --fail-fast doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=postgresql - - - name: 2FA Build & Test - run: ./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast -PdbType=postgresql - - - name: OAuth2 Build & Test - run: ./gradlew --no-daemon --console=plain :oauth2-tests:test --fail-fast -PdbType=postgresql + - name: Build & Test + run: | + ./gradlew --no-daemon --console=plain build test --fail-fast -x doc -x :twofactor-tests:test -x :oauth2-test:test -PdbType=postgresql + ./gradlew --no-daemon --console=plain :twofactor-tests:test --fail-fast -PdbType=postgresql + ./gradlew --no-daemon --console=plain :oauth2-tests:test --fail-fast -PdbType=postgresql - name: Archive test results if: always() uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 with: name: test-results - path: integration-tests/build/reports/ + path: | + integration-tests/build/reports/ + twofactor-tests/build/reports/ + oauth2-tests/build/reports/ - name: Archive server logs if: always() uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3 with: name: server-logs - path: integration-tests/build/cargo/ + path: | + integration-tests/build/cargo/ + twofactor-tests/build/cargo/ + oauth2-tests/build/cargo/ diff --git a/fineract-avro-schemas/build.gradle b/fineract-avro-schemas/build.gradle index dd113de95..ebea32170 100644 --- a/fineract-avro-schemas/build.gradle +++ b/fineract-avro-schemas/build.gradle @@ -76,4 +76,4 @@ test { sourceSets.main.java.srcDir new File(buildDir, "generated/java/src/main/java") licenseFormatMain.dependsOn buildJavaSdk -licenseMain.dependsOn buildJavaSdk +licenseMain.dependsOn licenseFormatMain diff --git a/fineract-avro-schemas/src/main/avro/MessageV1.avsc b/fineract-avro-schemas/src/main/avro/MessageV1.avsc index d20c5b626..df7679820 100644 --- a/fineract-avro-schemas/src/main/avro/MessageV1.avsc +++ b/fineract-avro-schemas/src/main/avro/MessageV1.avsc @@ -18,11 +18,6 @@ "doc": "The type of event the payload refers to. For example LoanApprovedBusinessEvent", "type": "string" }, - { - "name": "version", - "doc": "The version of the type of event. For example 1, 2, 3, etc", - "type": "string" - }, { "name": "category", "doc": "The category of event the payload refers to. For example LOAN", diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc index 966c670bb..a7b13b941 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc @@ -75,6 +75,14 @@ "long" ] }, + { + "default": null, + "name": "clientExternalId", + "type": [ + "null", + "string" + ] + }, { "default": null, "name": "loanProductId", diff --git a/fineract-client/build.gradle b/fineract-client/build.gradle index cc6871c4a..c95507a4d 100644 --- a/fineract-client/build.gradle +++ b/fineract-client/build.gradle @@ -157,4 +157,4 @@ sourceSets.main.java.srcDir new File(buildDir, "generated/java/src/main/java") // NOTE: Gradle suggested these dependencies licenseFormatMain.dependsOn buildJavaSdk -licenseMain.dependsOn buildJavaSdk +licenseMain.dependsOn licenseFormatMain diff --git a/fineract-doc/src/docs/en/chapters/architecture/reliable-event-framework.adoc b/fineract-doc/src/docs/en/chapters/architecture/reliable-event-framework.adoc new file mode 100644 index 000000000..5416b0f31 --- /dev/null +++ b/fineract-doc/src/docs/en/chapters/architecture/reliable-event-framework.adoc @@ -0,0 +1,442 @@ += Reliable event framework + +Fineract is capable of generating and raising events for external consumers in a reliable way. This section is going to describe all the details on that front with examples. + +== Framework capabilities +=== ACID (transactional) guarantee +The event framework must support ACID guarantees on the business operation level. + +Let’s see a simple use-case: + +1. A client applies to a loan on the UI +2. The loan is created on the server +3. A loan creation event is raised + +What happens if step 3 fails? Shall it fail the original loan creation process? + +What happens if step 2 fails but step 3 still gets executed? We’re raising an event for a loan that hasn’t been created in reality. + +Therefore, raising an event is tied to the original business transaction to ensure the data that’s getting written into the database along with the respective events are saved in an all-or-nothing fashion. + +=== Messaging integration +The system is able to send the raised events to downstream message channels. The current implementation supports the following message channels: + +* ActiveMQ + +=== Ordering guarantee +The events that are raised will be sent to the downstream message channels in the same order as they were raised. + +=== Delivery guarantee +The framework supports the at-least-once delivery guarantee for the raised events. + +=== Reliability and fault-tolerance +In terms of reliability and fault-tolerance, the event framework is able to handle the cases when the downstream message channel is not able to accept events. As soon as the message channel is back to operational, the events will be sent again. + +=== Selective event producing +Whether or not an event must be sent to downstream message channels for a particular Fineract instance is configurable through the UI and API. + +=== Standardized format +All the events sent to downstream message channels are conforming a standardized format using Avro schemas. + +=== Extendability and customizations +The event framework is capable of being easily extended with new events for additional business operations or customizing existing events. + +=== Ability to send events in bulk +The event framework makes it possible to sort of queue events until they are ready to be sent and send them as a single message instead of sending each event as a separate, individual one. + +For example during the COB process, there might be events raised in separate business steps which needs to be sent out but they only need to be sent out at the end of the COB execution process instead of one-by-one. + +== Architecture +=== Intro +On a high-level, the concept looks the following. An event gets raised in a business operation. The event data gets saved to the database - to ensure ACID guarantees. An asynchronous process takes the saved events from the database and puts them onto a message channel. + +The flow can be seen in the following diagram: + +image::{imagesdir}/reliable-event-framework-hla.png[] + +=== Foundational business events +The whole framework is built upon an existing infrastructure in Fineract; the Business Events. + +As a quick recap, Business Events are Fineract events that can be raised at any place in a business operation using the `BusinessEventNotifierService`. Callbacks can be registered when a certain type of Business Event is raised and other business operations can be done. For example when a Loan gets disbursed, there's an interested party doing the Loan Arrears Aging recalculation using the Business Event communication. + +The nice thing about the Business Events is that they are tied to the original transaction which means if any of the processing on the subscriber's side fail, the entire original transaction will be rolled back. This was one of the requirements for the Reliable event framework. + +=== Event database integration +The database plays a crucial part in the framework since to ensure transactionality, - without doing proper transaction synchronization between different message channels and the database - the framework is going to save all the raised events into the same relational database that Fineract is using. + +==== Database structure +The database structure looks the following + +[cols="1,1,1,1"] +|=== +|*Name* +|*Type* +|*Description* +|*Example* + +|`id` +|number +|Auto incremented ID. + +Not null. +|`1` + +|`type` +|text +|The event type as a string. + +Not null. +|`LoanApprovedBusinessEvent` + +|`schema` +|text +|The fully qualified name of the schema that was used for the data serialization, as a string. + +Not null. +|`org.apache.fineract.avro.loan.v1.LoanAccountDataV1` + +|`data` +|BLOB (MySQL/MariaDB), BYTEA (PostgreSQL) +|The event payload as Avro binary. + +Not null. +| + +|`created_at` +|timestamp +|UTC timestamp when the event was raised. + +Not null. +|`2022-09-06 14:20:10.148627 +00:00` + +|`status` +|text +|Enum text representing the status of the external event. + +Not null, indexed. +|`TO_BE_SENT`, `SENT` + +|`sent_at` +|timestamp +|UTC timestamp when the event was sent. +|`2022-09-06 14:30:10.148627 +00:00` + +|`idempotency_key` +|text +|Randomly generated UUID upon inserting a row into the table for idempotency purposes. + +Not null. +|`68aed085-8235-4722-b27d-b38674c19445` + +|`business_date` +|date +|The business date to when the event was generated. + +Not null, indexed. +|`2022-09-05` +|=== + +The above database table contains the unsent events which later on will be sent by an asynchronous event processor. + +Upon successfully sending an event, the corresponding statuses will be updated. + +=== Avro schemas +For serializing events, Fineract is using Apache Avro. There are 2 reasons for that: + +* More compact storage since Avro is a binary format +* The Avro schemas are published with Fineract as a separate JAR so event consumers can directly map the events into POJOs + +There are 3 different levels of Avro schemas used in Fineract for the Reliable event framework which are described below. + +==== Standard event schema +The standard event schema is for the regular events. These schemas are used when saving a raised event into the database and using the Avro schema to serialize the event data into a binary format. + +For example the OfficeDataV1 Avro schema looks the following: + +.`OfficeDataV1.avsc` +[%collapsible] +==== +[source,avroschema] +---- +include::{rootdir}/fineract-avro-schemas/src/main/avro/office/v1/OfficeDataV1.avsc[] +---- +==== + +==== Event message schema +The event message schema is just a wrapper around the standard event schema with extra metadata for the event consumers. + +Since Avro is strongly typed, the event content needs to be first serialized into a byte sequence and that needs to be wrapped around. + +This implies that for putting a single event message onto a message queue for external consumption, data needs to be serialized 2 times; this is the 2-level serialization. + +1. Serializing the event +2. Serializing the already serialized event into an event message using the message wrapper + +The message schema looks the following: + +.`MessageV1.avsc` +[source,avroschema] +---- +include::{rootdir}/fineract-avro-schemas/src/main/avro/MessageV1.avsc[] +---- + +==== Bulk event schema +The bulk event schema is used when multiple events are supposed to be sent together. This schema is used also when serializing the data for the database storing but the idea is quite simple. Have an array of other event schemas embedded into it. + +Since Avro is strongly typed, the array within the bulk event schema is an array of `MessageV1` schemas. That way the consumers can decide which events they want to deserialize and which don't. + +This elevates the regular 2-level serialization/deserialization concept up to a 3-level one: + +1. Serializing the standard events +2. Serializing the standard events into a bulk event +3. Serializing the bulk event into an event message + +==== Versioning +Avro is quite strict with changes to an existing schema and there are a number of compatibility modes available. + +Fineract keeps it simple though. Version numbers - in the package names and in the schema names - are increased with each published modification; meaning that if the OfficeDataV1 schema needs a new field and the `OfficeDataV1` schema has been published officially with Fineract, a new `OfficeDataV2` has to be created with the new field instead of modifying the existing schema. + +This pattern ensures that a certain event is always deserialized with the appropriate schema definition, otherwise the deserialization could fail. + +==== Code generation +The Avro schemas are described as JSON documents. That's hardly usable directly with Java hence Fineract generates Java POJOs from the Avro schemas. The good thing about these POJOs is the fact that they can be serialized/deserialized in themselves without any magic since they have a `toByteBuffer` and `fromByteBuffer` method. + +From POJO to ByteBuffer: +[source,java] +---- +LoanAccountDataV1 avroDto = ... +ByteBuffer buffer = avroDto.toByteBuffer(); +---- + +From ByteBuffer to POJO: +[source,java] +---- +ByteBuffer buffer = ... +LoanAccountDataV1 avroDto = LoanAccountDataV1.fromByteBuffer(buffer); +---- + +NOTE: The ByteBuffer is a stateful container and needs to be handled carefully. Therefore Fineract has a built-in ByteBuffer to byte array converter; `ByteBufferConverter`. + +==== Downstream event consumption +When consuming events on the other side of the message channel, it's critical to know which events the system is interested in. With the multi-level serialization, it's possible to deserialize only parts of the message and decide based on that whether it makes sense for a particular system to deserialize the event payload more. + +Whether events are important can be decided based on: + +* the `type` attribute in the message +* the `category` attribute in the message +* the `dataschema` attribute in the message + +These are the main attributes in the message wrapper one can use to decide whether an event message is useful. + +If the event needs to be deserialized, the next step is to find the corresponding schema definition. That's going to be sent in the `dataschema` attribute within the message wrapper. Since the attribute contains the fully-qualified name of the respective schema, it can be easily resolved to a Class object. Based on that class, the payload data can be easily deserialized using the `fromByteBuffer` method on every generated schema POJO. + +=== Message ordering +One of the requirements for the framework is to provide ordering guarantees. All the events have to conform a happens-before relation. + +For the downstream consumers, this can be verified by the `id` attribute within the messages. Since it's going to be a strictly-monotonic numeric sequence, it can be used for ordering purposes. + +=== Event categorization +For easier consumption, the terminology event category is introduced. This is nothing else but the bounded context an event is related to. + +For example the LoanApprovedBusinessEvent and the LoanWaiveInterestBusinessEvent are both related to the Loan bounded contexts. + +The category in which an event resides in is included in the message under the `category` attribute. + +The existing event categories can be found under the <> section. + +=== Asynchronous event processor +The events stored in the database will be picked up and sent by a regularly executed job. + +This job is a Fineract job, scheduled to run for every minute and will pick a number of events in order. Those events will be put onto the downstream message channel in the same order as they were raised. + +=== Purging events +The events database table is going to grow continuously. That's why Fineract has a purging functionality in place that's gonna delete old and already sent events. + +It's implemented as a Fineract job and is disabled by default. It's called TBD. + +== Usage +Using the event framework is quite simple. First, it has to be enabled through properties or environment variable. + +The respective options are the following: + +* the `fineract.events.external.enabled` property +* the `FINERACT_EXTERNAL_EVENTS_ENABLED` environment variable + +These configurations accept a boolean value; `true` or `false`. + +The key component to interact with is the `BusinessEventNotifierService#notifyPostBusinessEvent` method. + +=== Raising events +Raising events is really easy. An instance of a BusinessEvent interface is needed, that's going to be the event. There are plenty of them available already in the Fineract codebase. + +And that's pretty much it. Everything else is taken care of in terms of event data persisting and later on putting it onto a message channel. + +An example of event raising: +[source,java] +---- +@Override +public CommandProcessingResult createClient(final JsonCommand command) { + ... + businessEventNotifierService.notifyPostBusinessEvent(new ClientCreateBusinessEvent(newClient)); + ... + return ...; +} +---- + +NOTE: The above code is copied from the `ClientWritePlatformServiceJpaRepositoryImpl` class. + +==== Example event message content +Since the message is serialized into binary format, it's hard to represent in the documentation therefore here's a JSON representation of the data, just as an example. +[source,json] +---- +{ + "id": 121, + "source": "a65d759d-04f9-4ddf-ac52-34fa5d1f5a25", + "type": "LoanApprovedBusinessEvent", + "category": "Loan", + "createdAt": "2022-09-05T10:15:30", + "tenantId": "default", + "idempotencyKey": "abda146d-68b5-48ca-b527-16d2b7c5daef", + "dataschema": "org.apache.fineract.avro.loan.v1.LoanAccountDataV1", + "data": "..." +} +---- + +NOTE: The source attribute refers to an ID that's identifying the producer service. Fineract will regenerate this ID upon each application startup. + +=== Raising bulk events +Raising bulk events is really easy as well. The 2 key methods are: + +* `BusinessEventNotifierService#startExternalEventRecording` +* `BusinessEventNotifierService#stopExternalEventRecording` + +First, you have to start recording your events. This recording will be applied for the current thread. And then you can raise as many events as you want with the regular `BusinessEventNotifierService#notifyPostBusinessEvent` method, but they won't get saved to the database immediately. They'll get "recorded" into an internal buffer. + +When you stop recording using the method above, all the recorded events will be saved as a bulk event to the database; and serialized appropriately. + +From then on, the bulk event works just like any of the event. It'll be picked up by the processor to send it to a message channel. + +=== Event categories [[event-categories]] +TBD + +=== Selective event producing +TBD + +== Customizations +The framework provides a number of customization options: + +* Creating new events (that's already given by the Business Events) +* Creating new Avro schemas +* Customizing what data gets serialized for existing events + +In the upcoming sections, that's what going to be discussed. + +=== Creating new events +Creating new events is super easy. Just create an implementation of the `BusinessEvent` interface and that's it. + +From then on, you can raise those events in the system, although you can't publish them to an external message channel. If you have the event framework enabled, it's going to fail with not finding the appropriate serializer for your business event. + +NOTE: There are existing serializers which might be able to handle your new event. For example the `LoanBusinessEventSerializer` is capable of handling all `LoanBusinessEvent` subclasses so there's no need to create a brand new serializer. + +The interface looks the following: + +.`BusinessEvent.java` +[source,java] +---- +include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/BusinessEvent.java[lines=21..] +---- + +Quite simple. The `get` method should return the data you want to pass within the event instance. The `getType` method returns the name of the business event that's gonna be saved as the `type` into the database. + +NOTE: Creating a new business event only means that it can be used for raising an event. To make it compatible with the event framework and to be sent to a message channel, some extra work is needed which are described below. + +=== Creating new Avro schemas and serializers +First let's talk about the event serializers because that's what's needed to make a new event compatible with the framework. + +The serializer has a special interface, `BusinessEventSerializer`. + +.`BusinessEventSerializer.java` +[source,java] +---- +include::{rootdir}/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/BusinessEventSerializer.java[lines=25..] +---- + +An implementation of this interface shall be registered as a Spring bean, and it'll be picked up automatically by the framework. + +NOTE: You can look at the existing serializers for implementation ideas. + +New Avro schemas can be easily created. Just create a new Avro schema file in the `fineract-avro-schemas` project under the respective bounded context folder, and it will be picked up automatically by the code generator. + +=== BigDecimal support in Avro schemas +Apache Avro by default doesn't support complex types like a BigDecimal. It has to be implemented using a custom snippet like this: + +[source,avroschema] +---- +include::{rootdir}/fineract-avro-schemas/src/main/resources/avro-templates/bigdecimal.avsc[] +---- + +It's a 20 precision and 8 scale BigDecimal. + +Obviously it's quite challenging to copy-paste this snippet to every single BigDecimal field, so there's a customization in place for Fineract. +The type `bigdecimal` is supported natively, and you're free to use it like this: + +[source,avroschema] +---- +{ + "default": null, + "name": "principal", + "type": [ + "null", + "bigdecimal" + ] +} +---- + +NOTE: This `bigdecimal` type will be simple replaced with the BigDecimal snippet showed above during the compilation process. + +=== Custom data serialization for existing events +In case there's a need some extra bit of information within the event message that the default serializers are not providing, you can override this behavior by registering a brand-new custom serializer (as shown above). + +Since there's a priority order of serializers, the only thing the custom serializer need to do is to be annotated by the `@Order` annotation or to implement the `Ordered` interface. + +An example custom serializer with priority looks the following: +[source,java] +---- +@Component +@RequiredArgsConstructor +@Order(Ordered.HIGHEST_PRECEDENCE) +public class CustomLoanBusinessEventSerializer implements BusinessEventSerializer { + ... + + @Override + public boolean canSerialize(BusinessEvent event) { + return ...; + } + + @Override + public byte[] serialize(BusinessEvent rawEvent) throws IOException { + ... + ByteBuffer buffer = avroDto.toByteBuffer(); + return byteBufferConverter.convert(buffer); + } + + @Override + public Class getSupportedSchema() { + return ...; + } +} +---- + +NOTE: All the default serializers are having `Ordered.LOWEST_PRECEDENCE`. + +== Appendix A: Properties and environment variables +[cols="1,1,1,1"] +|=== +|Property name |Environment variable |Default value |Description + +|`fineract.events.external.enabled` +|`FINERACT_EXTERNAL_EVENTS_ENABLED` +|`false` +|Whether the external event sending is enabled or disabled. + +|=== \ No newline at end of file diff --git a/fineract-doc/src/docs/en/fineract-client.adoc b/fineract-doc/src/docs/en/fineract-client.adoc new file mode 100644 index 000000000..31a615164 --- /dev/null +++ b/fineract-doc/src/docs/en/fineract-client.adoc @@ -0,0 +1,6 @@ += Fineract Client Documentation + +[%collapsible] +==== +include::{rootdir}/fineract-client/build/generated/asciidoc/index.adoc[] +==== \ No newline at end of file diff --git a/fineract-doc/src/docs/en/images/reliable-event-framework-hla.png b/fineract-doc/src/docs/en/images/reliable-event-framework-hla.png new file mode 100644 index 0000000000000000000000000000000000000000..faad90bef4f6d9420f1f2ed7f00af0e906bec3f7 GIT binary patch literal 33122 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ÑNqS9u#v?JY`qxp%FAmh^spaqa&o;x9+a_;4 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 literal 0 HcmV?d00001 diff --git a/fineract-doc/src/docs/en/index.adoc b/fineract-doc/src/docs/en/index.adoc index c68bd3dd3..3cb04b341 100644 --- a/fineract-doc/src/docs/en/index.adoc +++ b/fineract-doc/src/docs/en/index.adoc @@ -47,7 +47,7 @@ include::chapters/release/index.adoc[leveloffset=+1] include::chapters/sdk/index.adoc[leveloffset=+1] -include::{rootdir}/fineract-client/build/generated/asciidoc/index.adoc[leveloffset=+1] +include::fineract-client.adoc[leveloffset=+1] include::faq.adoc[leveloffset=+1] diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index 79cc3e1b6..0d59d9f37 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -76,6 +76,7 @@ public final class LoanAccountData { private final Long clientId; private final String clientAccountNo; private final String clientName; + private final String clientExternalId; private final Long clientOfficeId; private final GroupGeneralData group; private final Long loanProductId; @@ -328,6 +329,7 @@ public final class LoanAccountData { this.clientAccountNo = null; this.clientName = null; this.clientOfficeId = null; + this.clientExternalId = null; this.group = null; this.loanProductId = null; this.loanProductName = null; @@ -455,6 +457,7 @@ public final class LoanAccountData { final String clientName = null; final String clientAccountNo = null; final Long clientOfficeId = null; + final String clientExternalId = null; final GroupGeneralData group = null; final EnumOptionData loanType = null; final Long loanProductId = null; @@ -566,34 +569,34 @@ public final class LoanAccountData { final CollectionData delinquent = CollectionData.template(); final DelinquencyRangeData delinquencyRange = null; - return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, - loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, - loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, - netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, - transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, - interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, - interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, - summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, - productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, - repaymentFrequencyDayOfWeekTypeOptions, transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, - amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, - chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, - loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, - canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, - inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, - isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, - closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled, - fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange); + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, + clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription, + isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + currencyData, proposedPrincipal, principal, principal, netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, + termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, + repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, + interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, + interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, + expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, + transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, + repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, + transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, + interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, + loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, + accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, + maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, + isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, + isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange); } /** * Used to produce a {@link LoanAccountData} with only client information defaulted. */ public static LoanAccountData clientDefaults(final Long clientId, final String clientAccountNo, final String clientName, - final Long clientOfficeId, final LocalDate expectedDisbursementDate) { + final Long clientOfficeId, final String clientExternalId, final LocalDate expectedDisbursementDate) { final Long id = null; final String accountNo = null; final LoanStatusEnumData status = null; @@ -713,36 +716,36 @@ public final class LoanAccountData { final CollectionData delinquent = CollectionData.template(); final DelinquencyRangeData delinquencyRange = null; - return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, - loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, - loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, - netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, - transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, - interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, - interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, - summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, - productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, - repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, - interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, - loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, - notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, - fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, - overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, - originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, - isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, - closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled, - fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange); + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, + clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription, + isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + currencyData, proposedPrincipal, principal, principal, netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, + termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, + repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, + interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, + interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, + expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, + transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, + repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, + repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, + interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, + loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, + accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, + maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, + isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, + isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange); } public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, clientAcc.clientId, clientAcc.clientAccountNo, - clientAcc.clientName, clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, - acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, - acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, - acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + clientAcc.clientName, clientAcc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, acc.loanProductId, + acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, + acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, + acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, @@ -781,6 +784,7 @@ public final class LoanAccountData { final String clientAccountNo = null; final String clientName = null; final Long clientOfficeId = null; + final String clientExternalId = null; final EnumOptionData loanType = null; final Long loanProductId = null; final String loanProductName = null; @@ -893,42 +897,43 @@ public final class LoanAccountData { final CollectionData delinquent = CollectionData.template(); final DelinquencyRangeData delinquencyRange = null; - return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, - loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, - loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, - netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, - transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, - interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, - interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, - summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, - productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, - repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, - interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, - loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, - notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, - fixedEmiAmount, maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, - overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, - originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, - isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, - closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled, - fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange); + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, + clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription, + isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + currencyData, proposedPrincipal, principal, principal, netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, + termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, + repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, + interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, + interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, + expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, + transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, + repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, + repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, + interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, + loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, + accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, + maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, + isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, + isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange); } public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, groupAcc.timeline, acc.summary, + acc.clientOfficeId, acc.clientExternalId, groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, @@ -966,6 +971,7 @@ public final class LoanAccountData { final String clientAccountNo = null; final String clientName = null; final Long clientOfficeId = null; + final String clientExternalId = null; final GroupGeneralData group = null; final EnumOptionData loanType = null; final String groupName = null; @@ -1087,12 +1093,12 @@ public final class LoanAccountData { final CollectionData delinquent = CollectionData.template(); final DelinquencyRangeData delinquencyRange = null; - return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, - loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(), - product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, - product.getCurrency(), proposedPrincipal, principal, principal, netDisbursalAmount, totalOverpaid, - product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments, product.getRepaymentEvery(), - product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, + clientExternalId, group, loanType, product.getId(), product.getName(), product.getDescription(), + product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, + loanOfficerId, loanOfficerName, product.getCurrency(), proposedPrincipal, principal, principal, netDisbursalAmount, + totalOverpaid, product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments, + product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), transactionProcessingStrategyName, product.getAmortizationType(), interestRatePerPeriod, product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(), @@ -1158,12 +1164,12 @@ public final class LoanAccountData { final DelinquencyRangeData delinquencyRange = null; return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), - product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), acc.loanPurposeId, - acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, product.getCurrency(), product.getPrincipal(), - product.getPrincipal(), product.getPrincipal(), netDisbursalAmount, acc.totalOverpaid, product.getInArrearsTolerance(), - termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(), product.getRepaymentEvery(), - product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), + acc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, product.getId(), product.getName(), + product.getDescription(), product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), + acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, product.getCurrency(), + product.getPrincipal(), product.getPrincipal(), product.getPrincipal(), netDisbursalAmount, acc.totalOverpaid, + product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(), + product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), product.getTransactionProcessingStrategyName(), product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(), @@ -1191,10 +1197,10 @@ public final class LoanAccountData { */ public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status, final String externalId, final Long clientId, final String clientAccountNo, final String clientName, final Long clientOfficeId, - final GroupGeneralData group, final EnumOptionData loanType, final Long loanProductId, final String loanProductName, - final String loanProductDescription, final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, - final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId, final String loanOfficerName, - final CurrencyData currencyData, final BigDecimal proposedPrincipal, final BigDecimal principal, + final String clientExternalId, final GroupGeneralData group, final EnumOptionData loanType, final Long loanProductId, + final String loanProductName, final String loanProductDescription, final boolean isLoanProductLinkedToFloatingRate, + final Long fundId, final String fundName, final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId, + final String loanOfficerName, final CurrencyData currencyData, final BigDecimal proposedPrincipal, final BigDecimal principal, final BigDecimal approvedPrincipal, final BigDecimal netDisbursalAmount, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance, final Integer termFrequency, final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, @@ -1256,21 +1262,22 @@ public final class LoanAccountData { final Boolean isRatesEnabled = false; final CollectionData delinquent = CollectionData.template(); - return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, - loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, - loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, - approvedPrincipal, netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, - numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, - transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, - annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, - allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, - loanSummary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, - calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, - repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, - interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, - loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loanCounter, loanProductCounter, - notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, + clientExternalId, group, loanType, loanProductId, loanProductName, loanProductDescription, + isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + currencyData, proposedPrincipal, principal, approvedPrincipal, netDisbursalAmount, totalOverpaid, inArrearsTolerance, + termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, + repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, + amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, + isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, + expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, + repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, + repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, + repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, + interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, + loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loanCounter, loanProductCounter, notes, + accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, @@ -1306,24 +1313,24 @@ public final class LoanAccountData { } return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, - acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, - productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, - repaymentFrequencyDayOfWeekTypeOptions, transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, - amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, - chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, - acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount, - disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, - acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, + acc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, + repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, + repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, + transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, + interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, + loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, + accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, + acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed, @@ -1348,22 +1355,22 @@ public final class LoanAccountData { public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, - acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, - acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, - acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, - acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, - acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, - acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, + acc.clientOfficeId, acc.clientExternalId, group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, + acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, + acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, + acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, @@ -1414,22 +1421,22 @@ public final class LoanAccountData { } return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, - acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, - acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, - acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, - acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, - acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, - acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, + acc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, + acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, + acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, + acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, @@ -1448,22 +1455,22 @@ public final class LoanAccountData { .withCalendarData(acc.interestRecalculationData, calendarData, compoundingCalendarData); return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, - acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, - acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, - acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, - acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, - acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, - acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, + acc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, + acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, + acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, + acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, @@ -1477,22 +1484,22 @@ public final class LoanAccountData { public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, calendarData.getRepeatsOnNthDayOfMonth(), calendarData.getRepeatsOnDay(), - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, - acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, - acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, - acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, - acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, - acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, - acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, + acc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + calendarData.getRepeatsOnNthDayOfMonth(), calendarData.getRepeatsOnDay(), acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, + acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, + acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, + acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, @@ -1507,22 +1514,22 @@ public final class LoanAccountData { public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, - acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, - acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, - acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.netDisbursalAmount, - acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, - acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, - acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, - acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, - acc.interestRateDifferential, acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, - acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, - acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, - acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, - acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, - acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, - acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, - acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, - acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, + acc.clientOfficeId, acc.clientExternalId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, + acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, + acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, + acc.approvedPrincipal, acc.netDisbursalAmount, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, + acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, + acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.allowPartialPeriodInterestCalculation, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, + acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, + acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, + acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, + acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, @@ -1538,7 +1545,8 @@ public final class LoanAccountData { final String accountNo, // final LoanStatusEnumData status, // final String externalId, // - final Long clientId, final String clientAccountNo, final String clientName, final Long clientOfficeId, // + final Long clientId, final String clientAccountNo, final String clientName, final Long clientOfficeId, + final String clientExternalId, // final GroupGeneralData group, final EnumOptionData loanType, final Long loanProductId, final String loanProductName, final String loanProductDescription, // final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, final Long loanPurposeId, @@ -1596,6 +1604,7 @@ public final class LoanAccountData { this.clientAccountNo = clientAccountNo; this.clientName = clientName; this.clientOfficeId = clientOfficeId; + this.clientExternalId = clientExternalId; this.group = group; this.loanType = loanType; this.loanProductId = loanProductId; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index bd7633b2a..45b1889eb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -388,7 +388,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final ClientData clientAccount = this.clientReadPlatformService.retrieveOne(clientId); final LocalDate expectedDisbursementDate = DateUtils.getBusinessLocalDate(); LoanAccountData loanTemplateDetails = LoanAccountData.clientDefaults(clientAccount.getId(), clientAccount.getAccountNo(), - clientAccount.getDisplayName(), clientAccount.getOfficeId(), expectedDisbursementDate); + clientAccount.getDisplayName(), clientAccount.getOfficeId(), clientAccount.getExternalId(), expectedDisbursementDate); if (productId != null) { final LoanProductData selectedProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId); @@ -612,7 +612,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " lp.allow_variabe_installments as isvariableInstallmentsAllowed, " + " lp.allow_multiple_disbursals as multiDisburseLoan," + " lp.can_define_fixed_emi_amount as canDefineInstallmentAmount," - + " c.id as clientId, c.account_no as clientAccountNo, c.display_name as clientName, c.office_id as clientOfficeId," + + " c.id as clientId, c.account_no as clientAccountNo, c.display_name as clientName, c.office_id as clientOfficeId, c.external_id as clientExternalId," + " g.id as groupId, g.account_no as groupAccountNo, g.display_name as groupName," + " g.office_id as groupOfficeId, g.staff_id As groupStaffId , g.parent_id as groupParentId, (select mg.display_name from m_group mg where mg.id = g.parent_id) as centerName, " + " g.hierarchy As groupHierarchy , g.level_id as groupLevel, g.external_id As groupExternalId, " @@ -725,6 +725,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Long clientId = JdbcSupport.getLong(rs, "clientId"); final String clientAccountNo = rs.getString("clientAccountNo"); final Long clientOfficeId = JdbcSupport.getLong(rs, "clientOfficeId"); + final String clientExternalId = rs.getString("clientExternalId"); final String clientName = rs.getString("clientName"); final Long groupId = JdbcSupport.getLong(rs, "groupId"); @@ -1010,7 +1011,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { DelinquencyRangeData delinquencyRange = this.delinquencyReadPlatformService.retrieveCurrentDelinquencyTag(id); return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, - clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, + clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, netDisbursalAmount, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, null, null, @@ -1482,7 +1483,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate expectedDisbursementDate = DateUtils.getBusinessLocalDate(); return LoanAccountData.clientDefaults(clientAccount.getId(), clientAccount.getAccountNo(), clientAccount.getDisplayName(), - clientAccount.getOfficeId(), expectedDisbursementDate); + clientAccount.getOfficeId(), clientAccount.getExternalId(), expectedDisbursementDate); } @Override